Inital commit v1.0

This commit is contained in:
2022-07-30 11:27:15 +02:00
parent 56ef2ce5ef
commit aa03af0861
30 changed files with 54655 additions and 0 deletions

96
src/GPData.cpp Normal file
View 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
View 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", &currentOctave, 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", &currentCapo, 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
View 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
View 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
View 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
View 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
View 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;
}