Inital commit v1.0
This commit is contained in:
96
src/GPData.cpp
Normal file
96
src/GPData.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "GPData.h"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace gpgui {
|
||||
namespace data {
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
VertexData GetRectData(float x, float y, float dx, float dy, float color) {
|
||||
return {
|
||||
dx, dy, color,
|
||||
x, dy, color,
|
||||
dx, y, color,
|
||||
|
||||
x, dy, color,
|
||||
x, y, color,
|
||||
dx, y, color,
|
||||
};
|
||||
}
|
||||
|
||||
VertexData GetCircleData(float centerX, float centerY, float radius, float color, int precision) {
|
||||
VertexData vertexData;
|
||||
vertexData.reserve(9 * precision);
|
||||
|
||||
for (int i = 0; i < precision; i++) {
|
||||
float theta = 2.0f * M_PI * float(i) / float(precision); // get the current angle
|
||||
float thetaNext = 2.0f * M_PI * float(i + 1) / float(precision); // get the next angle
|
||||
|
||||
float x = radius * cosf(theta); // calculate the x component
|
||||
float y = radius * sinf(theta); // calculate the y component
|
||||
|
||||
float dx = radius * cosf(thetaNext); // calculate the next x component
|
||||
float dy = radius * sinf(thetaNext); // calculate the next y component
|
||||
|
||||
vertexData.insert(vertexData.end(), {
|
||||
x + centerX, y + centerY, color,
|
||||
centerX, centerY, color,
|
||||
dx + centerX, dy + centerY, color,
|
||||
});
|
||||
}
|
||||
|
||||
return vertexData;
|
||||
}
|
||||
|
||||
float GetIntColor(const Color& color) {
|
||||
float out_color;
|
||||
int temp_color = color.red << 24 | color.green << 16 | color.blue << 8 | color.alpha;
|
||||
std::memcpy(&out_color, &temp_color, sizeof(float));
|
||||
return out_color;
|
||||
}
|
||||
|
||||
DrawData GetDrawData(const VertexData& vertexData) {
|
||||
DrawData drawData;
|
||||
|
||||
glGenVertexArrays(1, &drawData.vao);
|
||||
glGenBuffers(1, &drawData.vbo);
|
||||
|
||||
drawData.vertexCount = vertexData.size() / 3;
|
||||
|
||||
constexpr GLsizei stride = sizeof(float) * 3;
|
||||
|
||||
glBindVertexArray(drawData.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, drawData.vbo);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*)0);
|
||||
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(float) * 2));
|
||||
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glBindVertexArray(0);
|
||||
|
||||
return drawData;
|
||||
}
|
||||
|
||||
void UpdateData(DrawData& buffer, const VertexData& newData) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, newData.size() * sizeof(float), newData.data(), GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// OMFG 1 hour of debugging to add this line
|
||||
buffer.vertexCount = newData.size() / 3;
|
||||
}
|
||||
|
||||
void DrawVertexData(const DrawData& vertexData) {
|
||||
glBindVertexArray(vertexData.vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexData.vertexCount);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
} // namespace data
|
||||
} // namespace gpgui
|
||||
454
src/GPGui.cpp
Normal file
454
src/GPGui.cpp
Normal file
@@ -0,0 +1,454 @@
|
||||
#include "GPGui.h"
|
||||
#include "GPMusic.h"
|
||||
#include "GPRenderer.h"
|
||||
#include "GPData.h"
|
||||
#include "GPSave.h"
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <cmath>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace gpgui {
|
||||
namespace gui {
|
||||
|
||||
using Song = save::Song;
|
||||
using ChordSave = save::ChordSave;
|
||||
|
||||
using ChordType = music::ChordType;
|
||||
using ChordOffsets = music::ChordOffsets;
|
||||
using Tab = music::Tab;
|
||||
using Note = music::Note;
|
||||
|
||||
typedef std::shared_ptr<Song> SongPtr;
|
||||
|
||||
static bool pianoChordOnGuitar = true;
|
||||
|
||||
static int currentCapo = 0;
|
||||
|
||||
static const int CAPO_MIN = 0;
|
||||
static const int CAPO_MAX = 10;
|
||||
|
||||
static ChordSave currentChord;
|
||||
|
||||
static int fretMax = 5;
|
||||
static int currentOctave = 2;
|
||||
|
||||
static std::vector<SongPtr> loadedSongs;
|
||||
static SongPtr editSong = nullptr;
|
||||
|
||||
constexpr ImVec4 SAVE_COLOR{ 0, 0.5, 0, 1 };
|
||||
constexpr ImVec4 SAVE_HOVERED_COLOR{ 0, 0.7, 0, 1 };
|
||||
|
||||
constexpr ImVec4 DELETE_COLOR{ 0.5, 0, 0, 1 };
|
||||
constexpr ImVec4 DELETE_HOVERED_COLOR{ 0.7, 0, 0, 1 };
|
||||
|
||||
static void ApplyPianoChord(ChordType ct, std::uint8_t note, int octave, const ChordOffsets& notes) {
|
||||
renderer::ClearKeyboard();
|
||||
for (int i = 0; i < notes.size(); i++) {
|
||||
if (notes[i] == data::EMPTY_NOTE)
|
||||
continue;
|
||||
renderer::SetKeyHighlight(notes[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyTab(const Tab& tab) {
|
||||
renderer::ClearTab();
|
||||
for (int i = 0; i < tab.size(); i++) {
|
||||
renderer::SetTab(i, tab[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyTabPreview(const Tab& tab) {
|
||||
ApplyTab(tab);
|
||||
renderer::ClearKeyboard();
|
||||
for (int i = 0; i < tab.size(); i++) {
|
||||
if (tab[i] == data::EMPTY_TAB)
|
||||
continue;
|
||||
|
||||
if (tab[i] == 0) {
|
||||
renderer::SetKeyHighlight(music::GetStringOffset(i) + currentCapo, true);
|
||||
} else {
|
||||
renderer::SetKeyHighlight(music::GetStringOffset(i) + tab[i], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Tab GetTabChord(ChordType ct, std::uint8_t note, const ChordOffsets& notes) {
|
||||
if (pianoChordOnGuitar) {
|
||||
return music::FindPianoChord(notes, currentCapo, currentChord.fretMax);
|
||||
} else {
|
||||
return music::FindChord(music::GetChord(Note(note), ct), currentCapo);
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyGuitarChord(ChordType ct, std::uint8_t note, int octave, const ChordOffsets& notes) {
|
||||
Tab tab = GetTabChord(ct, note, notes);
|
||||
ApplyTab(tab);
|
||||
}
|
||||
|
||||
static void InverseChord(ChordOffsets& notes) {
|
||||
for (int i = 0; i < currentChord.inversion; i++) {
|
||||
std::uint8_t lastNote = notes[notes[3] == data::EMPTY_NOTE ? 2 : 3] - 12;
|
||||
std::uint8_t count = notes[3] == data::EMPTY_NOTE ? 2 : 3;
|
||||
for (int j = count; j > 0; j--) {
|
||||
notes[j] = notes[j - 1];
|
||||
}
|
||||
notes[0] = lastNote;
|
||||
}
|
||||
}
|
||||
|
||||
static ChordOffsets OffsetsToNote(const ChordOffsets& offsets, int octave) {
|
||||
ChordOffsets notes;
|
||||
for (int i = 0; i < offsets.size(); i++) {
|
||||
if (offsets[i] == data::EMPTY_NOTE) {
|
||||
notes[i] = data::EMPTY_NOTE;
|
||||
continue;
|
||||
}
|
||||
notes[i] = currentChord.note + offsets[i] + 12 * octave;
|
||||
}
|
||||
return notes;
|
||||
}
|
||||
|
||||
static void ApplyChord(ChordType ct, std::uint8_t note, int octave) {
|
||||
ChordOffsets offsets = GetChordOffsets(Note(note), ct);
|
||||
ChordOffsets notes = OffsetsToNote(offsets, octave);
|
||||
|
||||
InverseChord(notes);
|
||||
ApplyPianoChord(ct, note, octave, notes);
|
||||
ApplyGuitarChord(ct, note, octave, notes);
|
||||
|
||||
renderer::UpdateBuffers();
|
||||
}
|
||||
|
||||
static void RefreshRendering() {
|
||||
if (currentChord.note != Note::TOTAL && currentChord.type != ChordType::COUNT)
|
||||
ApplyChord(currentChord.type, static_cast<std::uint8_t>(currentChord.note), currentChord.octave);
|
||||
}
|
||||
|
||||
static void RenderChordButtons(ChordType ct) {
|
||||
for (int i = 0; i < Note::TOTAL; i++) {
|
||||
if (ImGui::Button(ToString(Note(i)).c_str())) {
|
||||
currentChord.note = Note(i);
|
||||
currentChord.type = ct;
|
||||
RefreshRendering();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 100);
|
||||
if (ImGui::Button("Inversion")) {
|
||||
currentChord.inversion = (currentChord.inversion + 1) % 4;
|
||||
RefreshRendering();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(": %i", currentChord.inversion);
|
||||
if (ImGui::SliderInt("Octave", ¤tOctave, 1, 3) && currentChord.note != Note::TOTAL) {
|
||||
currentChord.octave = currentOctave;
|
||||
RefreshRendering();
|
||||
}
|
||||
if (ImGui::SliderInt("Fret max", &fretMax, 3, 12)) {
|
||||
currentChord.fretMax = fretMax;
|
||||
RefreshRendering();
|
||||
}
|
||||
if (currentChord.note != Note::TOTAL) {
|
||||
ImGui::Text("Accord actuel : %s %s", ToString(Note(currentChord.note)).c_str(), ToString(ct).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderChordsTab() {
|
||||
if (ImGui::BeginTabItem("Accords")) {
|
||||
ImGui::BeginTabBar("Chords");
|
||||
for (int i = 0; i < static_cast<int>(ChordType::COUNT); i++) {
|
||||
ChordType ct = ChordType(i);
|
||||
if (ImGui::BeginTabItem(ToString(ct).c_str())) {
|
||||
ImGui::Text("%s :", ToString(ct).c_str());
|
||||
RenderChordButtons(ct);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
if (editSong != nullptr) {
|
||||
if (ImGui::Button("Ajouter l'accord")) {
|
||||
if (currentChord.note != Note::TOTAL) {
|
||||
editSong->chords.push_back(currentChord);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderOptions() {
|
||||
if (ImGui::BeginTabItem("Options")) {
|
||||
if (ImGui::Checkbox("Accords Guitaro-Piano ?", &pianoChordOnGuitar)) {
|
||||
currentChord.guitaroPiano = pianoChordOnGuitar;
|
||||
RefreshRendering();
|
||||
}
|
||||
if (editSong != nullptr) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
if (ImGui::InputInt("Capo", ¤tCapo, 1, 10)) {
|
||||
currentCapo = std::clamp(currentCapo, CAPO_MIN, CAPO_MAX);
|
||||
RefreshRendering();
|
||||
renderer::SetCapoPos(currentCapo);
|
||||
}
|
||||
if (editSong != nullptr) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderSaveSongButton(const SongPtr& song) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, SAVE_COLOR);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, SAVE_HOVERED_COLOR);
|
||||
if (ImGui::Button(std::string("Enregistrer##" + song->title).c_str())) {
|
||||
Song saveSong = *song;
|
||||
save::SaveSongToFile(saveSong, saveSong.title + ".gp");
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
static bool RenderDeleteSongButton(const SongPtr& song) {
|
||||
if (song == nullptr)
|
||||
return false;
|
||||
|
||||
|
||||
const std::string popupId = "DeleteSongConfirm" + song->title;
|
||||
|
||||
bool deleted = false;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, DELETE_COLOR);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, DELETE_HOVERED_COLOR);
|
||||
if (ImGui::Button(std::string("Supprimer##" + song->title).c_str())) {
|
||||
ImGui::OpenPopup(popupId.c_str());
|
||||
}
|
||||
if (ImGui::BeginPopup(popupId.c_str())) {
|
||||
ImGui::Text("Supprimer ?");
|
||||
if (ImGui::Button("Oui")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
deleted = true;
|
||||
std::string fileName = song->title + ".gp";
|
||||
if (fs::exists(fileName)) { // removing file if it exists
|
||||
fs::remove(fileName);
|
||||
}
|
||||
auto it = std::find(loadedSongs.begin(), loadedSongs.end(), song);
|
||||
loadedSongs.erase(it);
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, SAVE_COLOR);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, SAVE_HOVERED_COLOR);
|
||||
if (ImGui::Button("Non")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndPopup();
|
||||
} else {
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
static void RenderSongs() {
|
||||
if (loadedSongs.empty()) {
|
||||
ImGui::Text("Aucune chanson chargée");
|
||||
return;
|
||||
}
|
||||
ImGui::BeginChild("Songs");
|
||||
for (auto& song : loadedSongs) {
|
||||
ImGui::Text("%s (capo %i)", song->title.c_str(), song->capo);
|
||||
ImGui::SameLine();
|
||||
if (song == editSong) {
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::Button("Séléctionnée");
|
||||
ImGui::EndDisabled();
|
||||
} else {
|
||||
if (ImGui::Button(std::string("Sélectionner##" + song->title).c_str())) {
|
||||
editSong = song;
|
||||
currentCapo = song->capo;
|
||||
renderer::SetCapoPos(currentCapo);
|
||||
RefreshRendering();
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
RenderSaveSongButton(song);
|
||||
ImGui::SameLine();
|
||||
RenderDeleteSongButton(song);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
static void RenderNewSongPopup() {
|
||||
if (ImGui::BeginPopup("##New Song Popup")) {
|
||||
static char buffer[512];
|
||||
static int songCapo = 0;
|
||||
ImGui::InputText("Nom de la chanson", buffer, sizeof(buffer));
|
||||
ImGui::InputInt("Capo", &songCapo, 1);
|
||||
songCapo = std::clamp(songCapo, CAPO_MIN, CAPO_MAX);
|
||||
if (ImGui::Button("Créer une nouvelle chanson")) {
|
||||
loadedSongs.push_back(std::make_shared<Song>(buffer, static_cast<save::CapoPosType>(songCapo)));
|
||||
// resetting buffers
|
||||
songCapo = 0;
|
||||
std::fill(std::begin(buffer), std::end(buffer), 0);
|
||||
// closing popup
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
static void AddSongsInDirectory() {
|
||||
for (const auto& entry : fs::directory_iterator(".")) {
|
||||
auto path = entry.path();
|
||||
if (path.extension().string() == ".gp") {
|
||||
std::string fileName = path.filename().string();
|
||||
Song newSong = save::LoadSongFromFile(fileName);
|
||||
if (newSong.title.empty())
|
||||
continue;
|
||||
|
||||
auto it = std::find_if(loadedSongs.begin(), loadedSongs.end(), [&newSong](SongPtr song) {
|
||||
return newSong.title == song->title;
|
||||
});
|
||||
|
||||
if (it == loadedSongs.end()) { // add only if does not already exist
|
||||
loadedSongs.push_back(std::make_shared<Song>(newSong));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderSongsTab() {
|
||||
if (ImGui::BeginTabItem("Chansons")) {
|
||||
if (ImGui::Button("Nouveau")) {
|
||||
ImGui::OpenPopup("##New Song Popup");
|
||||
}
|
||||
RenderNewSongPopup();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Actualiser")) {
|
||||
AddSongsInDirectory();
|
||||
}
|
||||
ImGui::Separator();
|
||||
RenderSongs();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int HashTab(const Tab& tab) {
|
||||
return std::hash<unsigned int>()(tab[0] & 0xF << 20 | tab[1] & 0xF << 16
|
||||
| tab[2] & 0xF << 12 | tab[3] & 0xF << 8 | tab[4] & 0xF << 4
|
||||
| tab[5] & 0xF);
|
||||
}
|
||||
|
||||
static void RenderSongFrames() {
|
||||
static int eraseIndex = 0;
|
||||
for (int i = 0; i < editSong->chords.size(); i++) {
|
||||
ImGui::BeginChildFrame(100 + i * 100, ImVec2(200, 190));
|
||||
ImGui::Text(music::ToString(editSong->chords[i]).c_str());
|
||||
if (i > 0) {
|
||||
if (ImGui::Button("<-")) {
|
||||
auto previousTab = editSong->chords[i - 1];
|
||||
editSong->chords[i - 1] = editSong->chords[i];
|
||||
editSong->chords[i] = previousTab;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (i < editSong->chords.size() - 1) {
|
||||
if (ImGui::Button("->")) {
|
||||
auto nextTab = editSong->chords[i + 1];
|
||||
editSong->chords[i + 1] = editSong->chords[i];
|
||||
editSong->chords[i] = nextTab;
|
||||
}
|
||||
} else {
|
||||
ImGui::NewLine();
|
||||
}
|
||||
if (ImGui::Button("Visualiser")) {
|
||||
currentChord = editSong->chords[i];
|
||||
currentOctave = currentChord.octave;
|
||||
pianoChordOnGuitar = currentChord.guitaroPiano;
|
||||
RefreshRendering();
|
||||
}
|
||||
if (ImGui::Button("Supprimer")) {
|
||||
ImGui::OpenPopup("EraseTab");
|
||||
}
|
||||
if (ImGui::BeginPopup("EraseTab")) {
|
||||
ImGui::Text("Supprimer ?");
|
||||
if (ImGui::Button("Oui")) {
|
||||
editSong->chords.erase(editSong->chords.begin() + i);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Non")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::EndChildFrame();
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderEditTab() {
|
||||
if (ImGui::BeginTabItem("Edition")) {
|
||||
if (editSong == nullptr) {
|
||||
ImGui::Text("Sélectionnez une chanson pour commencer");
|
||||
} else {
|
||||
ImGui::Text("Edition de %s (capo %i)", editSong->title.c_str(), editSong->capo);
|
||||
|
||||
ImGui::BeginChild("SongContent", {}, false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderSongFrames();
|
||||
ImGui::NewLine();
|
||||
RenderSaveSongButton(editSong);
|
||||
ImGui::SameLine();
|
||||
if (RenderDeleteSongButton(editSong)) {
|
||||
editSong = nullptr;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Terminé")) {
|
||||
editSong = nullptr;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderInfos() {
|
||||
if (ImGui::BeginTabItem("Infos")) {
|
||||
ImGui::Text("FPS : %i", (int) std::ceil(ImGui::GetIO().Framerate));
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderTabs() {
|
||||
ImGui::BeginTabBar("MainTab");
|
||||
RenderChordsTab();
|
||||
RenderEditTab();
|
||||
RenderSongsTab();
|
||||
RenderOptions();
|
||||
RenderInfos();
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
void Render() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::Begin("Piano", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
|
||||
ImGui::SetWindowPos({ 0, 0 }, ImGuiCond_Always);
|
||||
ImGui::SetWindowSize({ io.DisplaySize.x, io.DisplaySize.y * 0.4f }, ImGuiCond_Always);
|
||||
RenderTabs();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Init() {
|
||||
AddSongsInDirectory();
|
||||
currentChord.octave = currentOctave; // adjust the slider
|
||||
currentChord.fretMax = fretMax;
|
||||
currentChord.guitaroPiano = pianoChordOnGuitar;
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace gpgui
|
||||
196
src/GPMusic.cpp
Normal file
196
src/GPMusic.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "GPMusic.h"
|
||||
#include "GPData.h"
|
||||
#include "GPSave.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#define ARRAY_SIZE(A) sizeof(A) / sizeof(A[0])
|
||||
|
||||
namespace gpgui {
|
||||
namespace music {
|
||||
|
||||
using ChordSave = save::ChordSave;
|
||||
|
||||
|
||||
// The positions of strings in a piano keyboard
|
||||
static const std::uint8_t Cordes[] = {
|
||||
7, 12, 17, 22, 26, 31
|
||||
};
|
||||
|
||||
int GetStringOffset(int string) {
|
||||
return Cordes[string];
|
||||
}
|
||||
|
||||
Note GetNote(std::uint8_t touche) {
|
||||
return Note(touche % 12);
|
||||
}
|
||||
|
||||
std::uint8_t GetOctave(std::uint8_t touche) {
|
||||
return touche / 12 + 1;
|
||||
}
|
||||
|
||||
std::string ToString(Note note) {
|
||||
switch (note) {
|
||||
case A:
|
||||
return "A";
|
||||
case Bb:
|
||||
return "A#";
|
||||
case B:
|
||||
return "B";
|
||||
case C:
|
||||
return "C";
|
||||
case Db:
|
||||
return "C#";
|
||||
case D:
|
||||
return "D";
|
||||
case Eb:
|
||||
return "D#";
|
||||
case E:
|
||||
return "E";
|
||||
case F:
|
||||
return "F";
|
||||
case Gb:
|
||||
return "F#";
|
||||
case G:
|
||||
return "G";
|
||||
case Ab:
|
||||
return "G#";
|
||||
default:
|
||||
return "wtf";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString(ChordType chord) {
|
||||
switch (chord) {
|
||||
case ChordType::Major:
|
||||
return "Majeur";
|
||||
case ChordType::Minor:
|
||||
return "Mineur";
|
||||
case ChordType::Dim:
|
||||
return "Dim";
|
||||
case ChordType::Sus:
|
||||
return "Sus";
|
||||
case ChordType::Major7:
|
||||
return "Majeur7";
|
||||
case ChordType::Minor7:
|
||||
return "Mineur7";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString(const Tab& tab) {
|
||||
std::string result;
|
||||
for (int fret : tab) {
|
||||
if (fret == data::EMPTY_TAB) {
|
||||
result += "X";
|
||||
} else {
|
||||
result += std::to_string(fret);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ToString(const ChordSave& chord) {
|
||||
return ToString(chord.note) + " " + ToString(chord.type);
|
||||
}
|
||||
|
||||
static bool isChord(int pos, const Chord& chord) {
|
||||
return GetNote(pos) == chord[0] || GetNote(pos) == chord[1] || GetNote(pos) == chord[2];
|
||||
}
|
||||
|
||||
Tab FindChord(const Chord& chord, int capo) {
|
||||
Tab tab;
|
||||
tab.fill(data::EMPTY_TAB);
|
||||
|
||||
for (int i = 0; i < tab.size(); i++) {
|
||||
int offset = 0;
|
||||
int cordePos = Cordes[i];
|
||||
|
||||
while (!isChord(cordePos + offset + capo, chord)) {
|
||||
offset++;
|
||||
}
|
||||
tab[i] = offset;
|
||||
// if unable to have a note beacuse of the capo :
|
||||
if (offset > 12 - capo) {
|
||||
tab[i] = data::EMPTY_TAB;
|
||||
}
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
Tab FindFakeChord(const Chord& chord, int capo, int pCorde) {
|
||||
Tab tab;
|
||||
tab.fill(data::EMPTY_TAB);
|
||||
|
||||
for (int i = pCorde; i < tab.size(); i++) {
|
||||
int offset = 0;
|
||||
int cordePos = Cordes[i];
|
||||
|
||||
while (GetNote(cordePos + offset + capo) != chord[(i - pCorde) % 3]) {
|
||||
offset++;
|
||||
}
|
||||
tab[i] = offset;
|
||||
if (offset > 12 - capo) {
|
||||
tab[i] = data::EMPTY_TAB;
|
||||
}
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
Tab FindPianoChord(const ChordOffsets& notes, int capo, int fretThreshold) {
|
||||
Tab tab;
|
||||
tab.fill(data::EMPTY_TAB);
|
||||
int pCorde = 0;
|
||||
for (int noteIndex = 0; noteIndex < notes.size(); noteIndex++) {
|
||||
int note = notes[noteIndex];
|
||||
if (note == data::EMPTY_NOTE) // empty chord
|
||||
continue;
|
||||
|
||||
//searching a string
|
||||
for (int i = pCorde; i < sizeof(Cordes); i++) {
|
||||
if (note >= Cordes[i] + capo) {
|
||||
int fret = note - (Cordes[i] + capo);
|
||||
if (fret <= fretThreshold - capo) {
|
||||
pCorde = i;
|
||||
tab[i] = note - Cordes[i];
|
||||
|
||||
if (note - (Cordes[i] + capo) == 0) // the fret is on the capo
|
||||
tab[i] = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
|
||||
Chord GetChord(Note note, ChordType type) {
|
||||
ChordOffsets offsets = GetChordOffsets(note, type);
|
||||
return { Note(note + offsets[0] % Note::TOTAL), Note(note + offsets[1] % Note::TOTAL), Note(note + offsets[2] % Note::TOTAL), offsets[3] == Note(data::EMPTY_NOTE) ? Note(data::EMPTY_NOTE) : Note(note + offsets[3]) };
|
||||
}
|
||||
|
||||
ChordOffsets GetChordOffsets(Note note, ChordType type) {
|
||||
switch (type) {
|
||||
case ChordType::Major:
|
||||
return { 0, 4, 7, data::EMPTY_NOTE };
|
||||
case ChordType::Minor:
|
||||
return { 0, 3, 7, data::EMPTY_NOTE };
|
||||
case ChordType::Dim:
|
||||
return { 0, 3, 6, data::EMPTY_NOTE };
|
||||
case ChordType::Sus:
|
||||
return { 0, 5, 7, data::EMPTY_NOTE };
|
||||
case ChordType::Major7:
|
||||
return { 0, 4, 7, 10 };
|
||||
case ChordType::Minor7:
|
||||
return { 0, 3, 7, 10 };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace music
|
||||
} // namespace gpgui
|
||||
370
src/GPRenderer.cpp
Normal file
370
src/GPRenderer.cpp
Normal file
@@ -0,0 +1,370 @@
|
||||
#include "GPRenderer.h"
|
||||
#include "GPData.h"
|
||||
#include "ShaderProgram.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace gpgui {
|
||||
namespace renderer {
|
||||
|
||||
using VertexData = data::VertexData;
|
||||
using Color = data::Color;
|
||||
|
||||
class GPShader : public ShaderProgram {
|
||||
public:
|
||||
GPShader() : ShaderProgram() {}
|
||||
|
||||
virtual void GetAllUniformLocation() {}
|
||||
};
|
||||
|
||||
const char vertexShader[] = R"(
|
||||
#version 330
|
||||
|
||||
layout (location = 0) in vec2 Position;
|
||||
layout (location = 1) in int Color;
|
||||
|
||||
flat out int pass_color;
|
||||
|
||||
void main() {
|
||||
pass_color = Color;
|
||||
gl_Position = vec4(Position.x * 2.0 - 1.0, Position.y * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
const char fragmentShader[] = R"(
|
||||
#version 330
|
||||
|
||||
flat in int pass_color;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = vec4(pass_color >> 24 & 0xFF, pass_color >> 16 & 0xFF, pass_color >> 8 & 0xFF, pass_color & 0xFF) / 255.0;
|
||||
}
|
||||
)";
|
||||
|
||||
enum KeyType : std::uint8_t {
|
||||
Major = 0,
|
||||
Minor
|
||||
};
|
||||
|
||||
static data::DrawData keyData, stringsData;
|
||||
static GPShader gpShader;
|
||||
static std::array<std::uint8_t, 7> highlitedKeys; // not 6 to avoid visual glitches with the strings
|
||||
static std::array<std::uint8_t, 6> highlitedStrings;
|
||||
static int capo = 0;
|
||||
|
||||
constexpr int KEY_NUMBER = 52;
|
||||
constexpr float KEYBOARD_HEIGHT = 0.3;
|
||||
constexpr float TAB_HEIGHT = 0.3;
|
||||
|
||||
bool IsKeyHighlited(int key) {
|
||||
bool result = (highlitedKeys[key / 8] >> (7 - (key % 8))) & 0x1;
|
||||
return result;
|
||||
}
|
||||
|
||||
void SetKeyHighlight(int key, bool highlight) {
|
||||
bool value = IsKeyHighlited(key);
|
||||
if (value == highlight)
|
||||
return;
|
||||
highlitedKeys[key / 8] ^= (1 << (7 - (key % 8)));
|
||||
}
|
||||
|
||||
void SetTab(int tab, int fret) {
|
||||
highlitedStrings[tab] = static_cast<std::uint8_t>(fret);
|
||||
}
|
||||
|
||||
void ClearKeyboard() {
|
||||
highlitedKeys.fill(0);
|
||||
}
|
||||
|
||||
void ClearTab() {
|
||||
highlitedStrings.fill(data::EMPTY_TAB);
|
||||
}
|
||||
|
||||
int GetKeyFromWhite(int whiteIndex) {
|
||||
int octave = whiteIndex / 7;
|
||||
int whiteKey = whiteIndex % 7;
|
||||
int offset = whiteKey * 2;
|
||||
if (whiteKey >= 2)
|
||||
offset--; // first hole
|
||||
if (whiteKey >= 5)
|
||||
offset--; // second hole
|
||||
return octave * 12 + offset;
|
||||
}
|
||||
|
||||
int GetKeyFromBlack(int blackIndex) {
|
||||
int octave = blackIndex / 7;
|
||||
int blackKey = blackIndex % 7;
|
||||
int offset = blackIndex * 2;
|
||||
// too lazy to do something good
|
||||
switch (blackKey) {
|
||||
case 0: {
|
||||
offset = 11;
|
||||
octave--;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
offset = 1;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
offset = 4;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
offset = 6;
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
offset = 9;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return octave * 12 + offset;
|
||||
}
|
||||
|
||||
void SetCapoPos(int capoPos) {
|
||||
capo = capoPos;
|
||||
}
|
||||
|
||||
VertexData GetKeyboardData() {
|
||||
VertexData vertexData;
|
||||
vertexData.reserve(2048);
|
||||
|
||||
constexpr Color WHITE{ 255, 255, 255 };
|
||||
constexpr Color BLACK{ 0, 0, 0 };
|
||||
constexpr Color GREEN{ 132, 255, 0 };
|
||||
constexpr Color DARK_GREEN{ 57, 190, 0 };
|
||||
|
||||
float whiteColor = data::GetIntColor(WHITE);
|
||||
|
||||
// white background
|
||||
VertexData backgroundData = data::GetRectData(0.0f, 0.0f, 1.0f, KEYBOARD_HEIGHT, whiteColor);
|
||||
vertexData.insert(vertexData.end(), backgroundData.begin(), backgroundData.end());
|
||||
|
||||
// highlited white keys
|
||||
constexpr int WHITE_KEYS_COUNT = 31;
|
||||
static float greenColor = data::GetIntColor(GREEN);
|
||||
for (int i = 0; i < WHITE_KEYS_COUNT; i++) {
|
||||
int touche = GetKeyFromWhite(i);
|
||||
|
||||
if (!IsKeyHighlited(touche))
|
||||
continue;
|
||||
|
||||
float x = (float)i / (float)WHITE_KEYS_COUNT;
|
||||
float dx = (float)(i + 1) / (float)WHITE_KEYS_COUNT;
|
||||
float y = 0.0f;
|
||||
float dy = KEYBOARD_HEIGHT;
|
||||
|
||||
VertexData rectData = data::GetRectData(x, y, dx, dy, greenColor);
|
||||
vertexData.insert(vertexData.end(), rectData.begin(), rectData.end());
|
||||
}
|
||||
|
||||
|
||||
// black borders
|
||||
constexpr float BORDER_THIKNESS = 0.001;
|
||||
constexpr int BORDER_COUNT = 31;
|
||||
static float color = data::GetIntColor(BLACK);
|
||||
|
||||
for (int i = 1; i < BORDER_COUNT; i++) {
|
||||
float centerX = (float)(i) / (float)BORDER_COUNT;
|
||||
float x = centerX - BORDER_THIKNESS / 2.0;
|
||||
float dx = centerX + BORDER_THIKNESS / 2.0;
|
||||
|
||||
VertexData rectData = data::GetRectData(x, 0.0f, dx, KEYBOARD_HEIGHT, color);
|
||||
vertexData.insert(vertexData.end(), rectData.begin(), rectData.end());
|
||||
}
|
||||
|
||||
// black keys
|
||||
constexpr float KEY_THIKNESS = 0.02;
|
||||
constexpr float KEY_HEIGHT = KEYBOARD_HEIGHT * 5.0 / 8.0;
|
||||
static float blackColor = data::GetIntColor(BLACK);
|
||||
|
||||
for (int i = 1; i < BORDER_COUNT; i++) {
|
||||
if (i % 7 != 2 && i % 7 != 5) {
|
||||
float centerX = (float)(i) / (float)BORDER_COUNT;
|
||||
float x = centerX - KEY_THIKNESS / 2.0;
|
||||
float dx = centerX + KEY_THIKNESS / 2.0;
|
||||
|
||||
VertexData rectData = data::GetRectData(x, KEYBOARD_HEIGHT - KEY_HEIGHT, dx, KEYBOARD_HEIGHT, blackColor);
|
||||
vertexData.insert(vertexData.end(), rectData.begin(), rectData.end());
|
||||
}
|
||||
}
|
||||
|
||||
// highlited black keys
|
||||
constexpr float KEY_HIGHLIGHT_BORDER = 0.002f;
|
||||
static float darkGreenColor = data::GetIntColor(DARK_GREEN);
|
||||
for (int i = 1; i < BORDER_COUNT; i++) {
|
||||
if (i % 7 != 2 && i % 7 != 5) {
|
||||
int touche = GetKeyFromBlack(i);
|
||||
|
||||
if (!IsKeyHighlited(touche))
|
||||
continue;
|
||||
|
||||
float centerX = (float)(i) / (float)BORDER_COUNT;
|
||||
float x = centerX - KEY_THIKNESS / 2.0;
|
||||
float dx = centerX + KEY_THIKNESS / 2.0;
|
||||
|
||||
VertexData rectData = data::GetRectData(x + KEY_HIGHLIGHT_BORDER, KEYBOARD_HEIGHT - KEY_HEIGHT + KEY_HIGHLIGHT_BORDER, dx - KEY_HIGHLIGHT_BORDER, KEYBOARD_HEIGHT, darkGreenColor);
|
||||
vertexData.insert(vertexData.end(), rectData.begin(), rectData.end());
|
||||
}
|
||||
}
|
||||
|
||||
return vertexData;
|
||||
}
|
||||
|
||||
VertexData GetStringsData() {
|
||||
VertexData vertexData;
|
||||
vertexData.reserve(2048);
|
||||
|
||||
constexpr float TAB_OFFSET = KEYBOARD_HEIGHT;
|
||||
|
||||
constexpr int STRING_COUNT = 6;
|
||||
|
||||
constexpr Color WHITE{ 255, 255, 255 };
|
||||
constexpr Color GREY{ 237, 231, 223 };
|
||||
constexpr Color SILVER{ 176, 160, 148 };
|
||||
constexpr Color BROWN{ 80, 55, 55 };
|
||||
constexpr Color GREEN{ 132, 255, 0 };
|
||||
constexpr Color DARK_GREEN{ 57, 190, 0 };
|
||||
constexpr Color CAPO{ 95, 95, 95 };
|
||||
|
||||
// brown background
|
||||
static const float brownColor = data::GetIntColor(BROWN);
|
||||
VertexData brownBackground = data::GetRectData(0.0f, TAB_OFFSET, 1.0f, TAB_OFFSET + TAB_HEIGHT, brownColor);
|
||||
vertexData.insert(vertexData.end(), brownBackground.begin(), brownBackground.end());
|
||||
|
||||
// the 12 frets
|
||||
constexpr int FRET_COUNT = 12;
|
||||
constexpr float FRET_THIKNESS = 0.01;
|
||||
constexpr float FRET_START = 0.105;
|
||||
|
||||
float silverColor = data::GetIntColor(SILVER);
|
||||
|
||||
for (int i = 0; i < FRET_COUNT; i++) {
|
||||
float centerX = FRET_START;
|
||||
for (int j = 0; j < i; j++) {
|
||||
centerX += FRET_START / std::pow(2, (float)j / 12.0f);
|
||||
}
|
||||
float x = centerX - FRET_THIKNESS / 2;
|
||||
float dx = centerX + FRET_THIKNESS / 2;
|
||||
|
||||
VertexData fretData = data::GetRectData(x, TAB_OFFSET, dx, TAB_OFFSET + TAB_HEIGHT, silverColor);
|
||||
vertexData.insert(vertexData.end(), fretData.begin(), fretData.end());
|
||||
}
|
||||
|
||||
// the 6 strings
|
||||
constexpr float STRING_THIKNESS = 0.01;
|
||||
static const float darkGreenColor = data::GetIntColor(DARK_GREEN);
|
||||
static const float greyColor = data::GetIntColor(GREY);
|
||||
|
||||
for (int i = 0; i < STRING_COUNT; i++) {
|
||||
float stringThikness = STRING_THIKNESS / (((float)i / 3.0f) + 1.0f);
|
||||
float centerY = TAB_OFFSET + (float)(i + 1) / (float)(STRING_COUNT + 1) * TAB_HEIGHT;
|
||||
float y = centerY - stringThikness / 2;
|
||||
float dy = centerY + stringThikness / 2;
|
||||
|
||||
VertexData stringData = data::GetRectData(0.0f, y, 1.0f, dy, highlitedStrings[i] == data::EMPTY_TAB ? greyColor : darkGreenColor);
|
||||
vertexData.insert(vertexData.end(), stringData.begin(), stringData.end());
|
||||
}
|
||||
|
||||
// drawing circles
|
||||
|
||||
constexpr float circleRadius = 1.0f / (float)(FRET_COUNT + 3.0f) * TAB_HEIGHT - STRING_THIKNESS / 2.0f;
|
||||
|
||||
auto getCircleCenterX = [](int fret) -> float {
|
||||
float circleCenterX = FRET_START;
|
||||
for (int i = 0; i < fret - 2; i++) {
|
||||
circleCenterX += FRET_START / std::pow(2, (float)i / 12.0f);
|
||||
}
|
||||
return circleCenterX + (FRET_START / std::pow(2, (float)((fret)-1.0f) / 12.0f)) / 2.0f;
|
||||
};
|
||||
|
||||
auto getCircleCenterY = [](int string) -> float {
|
||||
float centerY = TAB_OFFSET + (float)(string + 1) / (float)(STRING_COUNT + 1) * TAB_HEIGHT;
|
||||
centerY -= (1 / (float)(STRING_COUNT + 1) * TAB_HEIGHT) / 2.0f;
|
||||
return centerY;
|
||||
};
|
||||
|
||||
static const std::vector<int> circlesX = { 3, 5, 7, 9 };
|
||||
for (int i = 0; i < circlesX.size(); i++) {
|
||||
auto circleData = data::GetCircleData(getCircleCenterX(circlesX[i]), TAB_OFFSET + TAB_HEIGHT / 2.0f, circleRadius, data::GetIntColor(WHITE), 20);
|
||||
vertexData.insert(vertexData.end(), circleData.begin(), circleData.end());
|
||||
}
|
||||
|
||||
static const std::vector<int> circlesY = { 1, 5 };
|
||||
for (int i = 0; i < circlesY.size(); i++) {
|
||||
auto circleData = data::GetCircleData(getCircleCenterX(12), getCircleCenterY(circlesY[i]), circleRadius, data::GetIntColor(WHITE), 20);
|
||||
vertexData.insert(vertexData.end(), circleData.begin(), circleData.end());
|
||||
}
|
||||
|
||||
// fret highlight
|
||||
float greenColor = data::GetIntColor(GREEN);
|
||||
for (int i = 0; i < STRING_COUNT; i++) {
|
||||
|
||||
if (highlitedStrings[i] == data::EMPTY_TAB || highlitedStrings[i] == 0)
|
||||
continue;
|
||||
|
||||
int position = highlitedStrings[i];
|
||||
|
||||
float centerY = TAB_OFFSET + (float)(i + 1) / (float)(STRING_COUNT + 1) * TAB_HEIGHT;
|
||||
float y = centerY - (1.0f / (float)(STRING_COUNT + 1) * TAB_HEIGHT) / 2;
|
||||
float dy = centerY + (1.0f / (float)(STRING_COUNT + 1) * TAB_HEIGHT) / 2;
|
||||
|
||||
float centerX = FRET_START;
|
||||
for (int j = 0; j < position - 1; j++) {
|
||||
centerX += FRET_START / std::pow(2, (float)j / 12.0f);
|
||||
}
|
||||
float x = centerX - FRET_THIKNESS / 2 * 6;
|
||||
float dx = centerX - FRET_THIKNESS / 2;
|
||||
|
||||
VertexData fretData = data::GetRectData(x, y, dx, dy, greenColor);
|
||||
vertexData.insert(vertexData.end(), fretData.begin(), fretData.end());
|
||||
}
|
||||
|
||||
// draw capo
|
||||
static float capoColor = data::GetIntColor(CAPO);
|
||||
constexpr float CAPO_OFFSET = 0.01;
|
||||
|
||||
if (capo != 0) {
|
||||
float centerX = FRET_START;
|
||||
for (int j = 0; j < capo - 1; j++) {
|
||||
centerX += FRET_START / std::pow(2, (float)j / 12.0f);
|
||||
}
|
||||
centerX -= CAPO_OFFSET;
|
||||
float x = centerX - FRET_THIKNESS / 2 * 6;
|
||||
float dx = centerX - FRET_THIKNESS / 2;
|
||||
|
||||
VertexData fretData = data::GetRectData(x, TAB_OFFSET, dx, TAB_OFFSET + TAB_HEIGHT, capoColor);
|
||||
vertexData.insert(vertexData.end(), fretData.begin(), fretData.end());
|
||||
}
|
||||
|
||||
return vertexData;
|
||||
}
|
||||
|
||||
void InitRendering() {
|
||||
gpShader.LoadProgram(vertexShader, fragmentShader);
|
||||
|
||||
ClearTab();
|
||||
|
||||
keyData = data::GetDrawData(GetKeyboardData());
|
||||
stringsData = data::GetDrawData(GetStringsData());
|
||||
}
|
||||
|
||||
void UpdateBuffers() {
|
||||
data::UpdateData(keyData, GetKeyboardData());
|
||||
data::UpdateData(stringsData, GetStringsData());
|
||||
}
|
||||
|
||||
void DrawWidgets() {
|
||||
gpShader.Start();
|
||||
data::DrawVertexData(keyData);
|
||||
data::DrawVertexData(stringsData);
|
||||
gpShader.Stop();
|
||||
}
|
||||
|
||||
} // namespace renderer
|
||||
} // namespace gpgui
|
||||
92
src/GPSave.cpp
Normal file
92
src/GPSave.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "GPSave.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace gpgui {
|
||||
namespace save {
|
||||
|
||||
static constexpr std::uint8_t SAVE_VERSION = 0;
|
||||
|
||||
typedef std::vector<std::uint8_t> DataBuffer;
|
||||
typedef std::uint16_t SongSizeType;
|
||||
|
||||
template<typename T>
|
||||
static void WriteData(DataBuffer& buffer, const T* data, std::size_t dataSize = sizeof(T)) {
|
||||
std::size_t endPos = buffer.size();
|
||||
|
||||
buffer.resize(endPos + dataSize);
|
||||
std::memcpy(buffer.data() + endPos, data, dataSize);
|
||||
}
|
||||
|
||||
static void WriteFile(const DataBuffer& buffer, const std::string& fileName) {
|
||||
std::ofstream fileStream(fileName);
|
||||
|
||||
if (!fileStream)
|
||||
return;
|
||||
|
||||
fileStream.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
|
||||
}
|
||||
|
||||
void SaveSongToFile(const Song& song, const std::string& fileName) {
|
||||
DataBuffer buffer;
|
||||
|
||||
WriteData(buffer, &SAVE_VERSION); // writing file version
|
||||
|
||||
WriteData(buffer, &song.capo); // writing the capo offset as 8 bit unsigned integer
|
||||
|
||||
SongSizeType songSize = song.chords.size();
|
||||
WriteData(buffer, &songSize); // writing the size as 16 bit unsigned int
|
||||
|
||||
WriteData(buffer, song.chords.data(), songSize * sizeof(ChordSave));
|
||||
|
||||
WriteFile(buffer, fileName);
|
||||
}
|
||||
|
||||
static Song LoadSongVersion0(const std::string& data, std::size_t offset, const std::string& filePath) {
|
||||
Song song{ "", 0 };
|
||||
|
||||
std::memcpy(&song.capo, data.data() + offset, sizeof(song.capo)); // reading capo pos
|
||||
offset += sizeof(song.capo);
|
||||
|
||||
SongSizeType songSize;
|
||||
std::memcpy(&songSize, data.data() + offset, sizeof(songSize)); // reading song size
|
||||
|
||||
offset += sizeof(songSize);
|
||||
|
||||
song.chords.resize(songSize);
|
||||
std::memcpy(song.chords.data(), data.data() + offset, songSize * sizeof(ChordSave)); // reading chords
|
||||
|
||||
song.title = filePath.substr(0, filePath.find_last_of('.'));
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
Song LoadSongFromFile(const std::string& filePath) {
|
||||
std::ifstream fileStream(filePath);
|
||||
|
||||
if (!fileStream)
|
||||
return { "", 0 };
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << fileStream.rdbuf();
|
||||
|
||||
std::string str = oss.str();
|
||||
|
||||
std::uint8_t fileVersion;
|
||||
std::memcpy(&fileVersion, str.data(), sizeof(fileVersion)); // reading file save version
|
||||
|
||||
std::size_t offset = sizeof(fileVersion);
|
||||
|
||||
switch (fileVersion) {
|
||||
case 0:
|
||||
return LoadSongVersion0(str, offset, filePath);
|
||||
|
||||
default:
|
||||
return { "", 0 };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace save
|
||||
} // namespace gpgui
|
||||
91
src/ShaderProgram.cpp
Executable file
91
src/ShaderProgram.cpp
Executable file
@@ -0,0 +1,91 @@
|
||||
#include "ShaderProgram.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace gpgui {
|
||||
|
||||
ShaderProgram::ShaderProgram() :
|
||||
m_ProgramID(0), m_VertexShaderID(0), m_FragmentShaderID(0) {
|
||||
}
|
||||
|
||||
ShaderProgram::~ShaderProgram() {
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
void ShaderProgram::Start() const {
|
||||
glUseProgram(m_ProgramID);
|
||||
}
|
||||
|
||||
void ShaderProgram::Stop() const {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void ShaderProgram::CleanUp() const {
|
||||
Stop();
|
||||
glDetachShader(m_ProgramID, m_VertexShaderID);
|
||||
glDetachShader(m_ProgramID, m_FragmentShaderID);
|
||||
glDeleteShader(m_VertexShaderID);
|
||||
glDeleteShader(m_FragmentShaderID);
|
||||
glDeleteProgram(m_ProgramID);
|
||||
}
|
||||
|
||||
void ShaderProgram::LoadProgramFile(const std::string& vertexFile,
|
||||
const std::string& fragmentFile) {
|
||||
m_VertexShaderID = static_cast<unsigned int>(LoadShaderFromFile(vertexFile, static_cast<unsigned int>(GL_VERTEX_SHADER)));
|
||||
m_FragmentShaderID = static_cast<unsigned int>(LoadShaderFromFile(fragmentFile, static_cast<unsigned int>(GL_FRAGMENT_SHADER)));
|
||||
m_ProgramID = glCreateProgram();
|
||||
glAttachShader(m_ProgramID, m_VertexShaderID);
|
||||
glAttachShader(m_ProgramID, m_FragmentShaderID);
|
||||
glLinkProgram(m_ProgramID);
|
||||
glValidateProgram(m_ProgramID);
|
||||
GetAllUniformLocation();
|
||||
}
|
||||
|
||||
void ShaderProgram::LoadProgram(const std::string& vertexSource,
|
||||
const std::string& fragmentSource) {
|
||||
m_VertexShaderID = static_cast<unsigned int>(LoadShader(vertexSource, static_cast<unsigned int>(GL_VERTEX_SHADER)));
|
||||
m_FragmentShaderID = static_cast<unsigned int>(LoadShader(fragmentSource, static_cast<unsigned int>(GL_FRAGMENT_SHADER)));
|
||||
m_ProgramID = glCreateProgram();
|
||||
glAttachShader(m_ProgramID, m_VertexShaderID);
|
||||
glAttachShader(m_ProgramID, m_FragmentShaderID);
|
||||
glLinkProgram(m_ProgramID);
|
||||
glValidateProgram(m_ProgramID);
|
||||
GetAllUniformLocation();
|
||||
}
|
||||
|
||||
unsigned int ShaderProgram::LoadShader(const std::string& source, unsigned int type) {
|
||||
unsigned int shaderID = glCreateShader(GLenum(type));
|
||||
|
||||
const char* c_str = source.c_str();
|
||||
int* null = 0;
|
||||
glShaderSource(shaderID, 1, &c_str, null);
|
||||
glCompileShader(shaderID);
|
||||
GLint compilesuccessful;
|
||||
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compilesuccessful);
|
||||
if (compilesuccessful == false) {
|
||||
GLsizei size;
|
||||
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &size);
|
||||
std::vector<char> shaderError(static_cast<std::size_t>(size));
|
||||
glGetShaderInfoLog(shaderID, size, &size, shaderError.data());
|
||||
}
|
||||
return shaderID;
|
||||
}
|
||||
|
||||
unsigned int ShaderProgram::LoadShaderFromFile(const std::string& file, unsigned int type) {
|
||||
std::stringstream stream;
|
||||
std::ifstream fileStream(file);
|
||||
|
||||
if (fileStream) {
|
||||
stream << fileStream.rdbuf();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return LoadShader(stream.str(), type);
|
||||
}
|
||||
|
||||
} // namespace gpgui
|
||||
144
src/main.cpp
Normal file
144
src/main.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// Dear ImGui: standalone example application for GLFW + OpenGL 3, using programmable pipeline
|
||||
// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.)
|
||||
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
|
||||
// Read online: https://github.com/ocornut/imgui/tree/master/docs
|
||||
|
||||
#include "imgui.h"
|
||||
#include "backends/imgui_impl_glfw.h"
|
||||
#include "backends/imgui_impl_opengl3.h"
|
||||
#include <GL/glew.h>
|
||||
#include <stdio.h>
|
||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||
#include <GLES2/gl2.h>
|
||||
#endif
|
||||
#include <GLFW/glfw3.h> // Will drag system OpenGL headers
|
||||
|
||||
// [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to maximize ease of testing and compatibility with old VS compilers.
|
||||
// To link with VS2010-era libraries, VS2015+ requires linking with legacy_stdio_definitions.lib, which we do using this pragma.
|
||||
// Your own project should not be affected, as you are likely to link with a newer binary of GLFW that is adequate for your version of Visual Studio.
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
|
||||
#pragma comment(lib, "legacy_stdio_definitions")
|
||||
#endif
|
||||
|
||||
#include "GPGui.h"
|
||||
#include "GPRenderer.h"
|
||||
|
||||
static void glfw_error_callback(int error, const char* description)
|
||||
{
|
||||
fprintf(stderr, "Glfw Error %d: %s\n", error, description);
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit())
|
||||
return 1;
|
||||
|
||||
// Decide GL+GLSL versions
|
||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||
// GL ES 2.0 + GLSL 100
|
||||
const char* glsl_version = "#version 100";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
||||
#elif defined(__APPLE__)
|
||||
// GL 3.2 + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
const char* glsl_version = "#version 130";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only
|
||||
#endif
|
||||
|
||||
// Create window with graphics context
|
||||
GLFWwindow* window = glfwCreateWindow(1920, 1080, "GuitaroPianoGui", NULL, NULL);
|
||||
if (window == NULL)
|
||||
return 1;
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1); // Enable vsync
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
||||
|
||||
// Setup Dear ImGui style
|
||||
ImGui::StyleColorsDark();
|
||||
//ImGui::StyleColorsClassic();
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
// Load Fonts
|
||||
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
|
||||
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
|
||||
// - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
|
||||
// - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
|
||||
// - Read 'docs/FONTS.md' for more instructions and details.
|
||||
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
|
||||
//io.Fonts->AddFontDefault();
|
||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
|
||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
|
||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
|
||||
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
|
||||
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
|
||||
//IM_ASSERT(font != NULL);
|
||||
|
||||
// Our state
|
||||
bool show_demo_window = true;
|
||||
bool show_another_window = false;
|
||||
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
|
||||
gpgui::renderer::InitRendering();
|
||||
gpgui::gui::Init();
|
||||
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
// Poll and handle events (inputs, window resize, etc.)
|
||||
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
||||
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
|
||||
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
|
||||
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
||||
glfwPollEvents();
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Rendering
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
gpgui::gui::Render();
|
||||
gpgui::renderer::DrawWidgets();
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user