├── res ├── resource.rc └── icon.ico ├── .gitignore ├── .gitmodules ├── src ├── Piano.h ├── util.h ├── resource.h ├── settings.h ├── themes.h ├── Midi.h ├── Log.h ├── inpututils.h ├── Qwerty.h ├── Piano.cpp ├── porttime.h ├── util.cpp ├── Log.cpp ├── Midi.cpp ├── settings.cpp ├── themes.cpp ├── inpututils.cpp └── main.cpp ├── cmake └── FindPortMidi.cmake ├── LICENSE.md ├── README.md └── CMakeLists.txt /res/resource.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.aps 2 | *.filters 3 | *.vcxproj 4 | *.user 5 | 6 | build/ 7 | -------------------------------------------------------------------------------- /res/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArijanJ/miditoqwerty/HEAD/res/icon.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gl3w"] 2 | path = gl3w 3 | url = https://github.com/skaslev/gl3w 4 | branch = master 5 | [submodule "imgui"] 6 | path = imgui 7 | url = https://github.com/ocornut/imgui 8 | branch = master 9 | [submodule "portmidi"] 10 | path = portmidi 11 | url = https://github.com/PortMidi/portmidi 12 | branch = master 13 | -------------------------------------------------------------------------------- /src/Piano.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 22/4/20. 3 | // 4 | 5 | #ifndef MIDI_PIANO_H 6 | #define MIDI_PIANO_H 7 | 8 | 9 | #include 10 | 11 | class Piano { 12 | int key_states[256] = {0}; 13 | public: 14 | void up(int key); 15 | void draw(bool *show, bool windowsEditable, ImU32 noteColor); 16 | void down(int key, int velocity); 17 | std::vector current_notes(); 18 | }; 19 | 20 | 21 | #endif //MIDI_PIANO_H 22 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 25/4/20. 3 | // 4 | #include 5 | 6 | #include 7 | 8 | #ifndef MIDI_UTIL_H 9 | #define MIDI_UTIL_H 10 | 11 | //ImVec4 getRainbow(float* isRed, float* isGreen, float* isBlue); 12 | void advanceRainbow(int* isRed, int* isGreen, int* isBlue); 13 | std::string timestampString(PmTimestamp timestamp); 14 | std::string midiNoteString(uint8_t note); 15 | std::string midiChordString(std::vector notes); 16 | 17 | #endif //MIDI_UTIL_H 18 | -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by imguimidiqwerty.rc 4 | // 5 | #define IDI_ICON1 109 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 110 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "themes.h" 8 | 9 | class Setting { 10 | std::string name; 11 | int* valueRef; 12 | public: 13 | Setting(std::string name, int* valueRef); 14 | std::string GetName(); 15 | void* GetValue(); 16 | void SetValue(int value); 17 | }; 18 | 19 | class SettingsHandler { 20 | std::vector settings; // lol 21 | public: 22 | bool LoadSettings(); 23 | void AddSetting(std::string name, int* valueRef); 24 | 25 | Setting* GetSetting(std::string name); 26 | int* GetValue(std::string name); 27 | bool DumpSettings(); 28 | }; 29 | -------------------------------------------------------------------------------- /src/themes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | extern ImVec4 gBackgroundColor; 11 | extern ImVec4 gNoteColor; 12 | extern ImVec4 gNoteNameColor; 13 | 14 | extern std::string currentFont; 15 | extern std::string currentTheme; 16 | 17 | extern std::string defaultFont; 18 | extern std::string defaultTheme; 19 | 20 | int LoadTheme(std::string path); 21 | bool ShowThemeSelector(const char* label, std::string& output); 22 | void ImGui::ShowFontSelector(const char* label); 23 | bool ImGui::ShowStyleSelector(const char* label); 24 | void ImGui::ShowStyleEditor(ImGuiStyle* ref); -------------------------------------------------------------------------------- /src/Midi.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 22/4/20. 3 | // 4 | 5 | #ifndef MIDI_MIDI_H 6 | #define MIDI_MIDI_H 7 | 8 | #include 9 | 10 | #include 11 | #include "porttime.h" 12 | 13 | extern PmTimestamp lastNotePlayed; 14 | 15 | class Midi { 16 | public: 17 | PmDeviceID deviceID; 18 | PortMidiStream* stream; 19 | virtual ~Midi(); 20 | void shutdown(PortMidiStream* stream); 21 | 22 | private: 23 | PmEvent buffer[1024]; 24 | public: 25 | void InitWrapper(); 26 | void poll(std::function callback, bool debug = false); 27 | Midi(PmDeviceID passedID = -1); //constructor 28 | 29 | }; 30 | 31 | 32 | #endif //MIDI_MIDI_H 33 | -------------------------------------------------------------------------------- /src/Log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 25/4/20. 3 | // 4 | 5 | #ifndef MIDI_LOG_H 6 | #define MIDI_LOG_H 7 | 8 | #include "imgui.h" 9 | 10 | extern int logStuff; 11 | 12 | class Log { 13 | private: 14 | ImGuiTextBuffer Buf; 15 | ImGuiTextFilter Filter; 16 | ImVector LineOffsets; // Index to lines offset. We maintain this with AddLog() calls, allowing us to have a random access on lines 17 | bool AutoScroll; // Keep scrolling if already at the bottom 18 | void Clear(); 19 | public: 20 | void AddLog(const char* fmt, ...) IM_FMTARGS(2); 21 | void Draw(const char *title, bool *p_open = nullptr); 22 | Log(); 23 | }; 24 | 25 | 26 | #endif //MIDI_LOG_H 27 | -------------------------------------------------------------------------------- /cmake/FindPortMidi.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find PortMidi 2 | # Once done, this will define 3 | # 4 | # PortMidi_FOUND - system has PortMidi 5 | # PortMidi_INCLUDE_DIRS - the PortMidi include directories 6 | # PortMidi_LIBRARIES - link these to use PortMidi 7 | # PortMidi_VERSION - detected version of PortMidi 8 | # 9 | # See documentation on how to write CMake scripts at 10 | # http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries 11 | 12 | find_library(PORTMIDI_LIBRARY portmidi 13 | HINTS 14 | $ENV{PORTMIDI_DIR} 15 | ) 16 | 17 | find_path(PORTMIDI_INCLUDE_DIR portmidi.h 18 | HINTS 19 | $ENV{PORTMIDI_DIR} 20 | ) 21 | 22 | find_path( PORTTIME_INCLUDE_DIR porttime.h 23 | HINTS 24 | $ENV{PORTMIDI_DIR} 25 | ) 26 | 27 | set( PORTMIDI_LIBRARIES ${PORTMIDI_LIBRARY} ) 28 | 29 | include(FindPackageHandleStandardArgs) 30 | 31 | find_package_handle_standard_args( PortMidi REQUIRED_VARS PORTMIDI_LIBRARIES PORTMIDI_INCLUDE_DIR PORTTIME_INCLUDE_DIR ) 32 | 33 | mark_as_advanced( PORTMIDI_LIBRARY ) -------------------------------------------------------------------------------- /src/inpututils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | // http://www.quadibloc.com/comp/scan.htm 7 | extern std::unordered_map scanSet1Map; 8 | extern std::unordered_map scanSet2Map; 9 | 10 | extern int scanSetChoice; 11 | 12 | void loadScansets(); 13 | 14 | HKL getLayout(); 15 | 16 | void setupInput(INPUT input[], int size, int code1, int code2, bool advanced); 17 | 18 | unsigned int getScanCode(int code); 19 | 20 | unsigned int getScanCodeChar(char c); 21 | 22 | void sendKeyDown(char c); 23 | 24 | void sendKeyUp(char c, char location = 'm'); 25 | 26 | void sendOutOfRangeKey(char c); 27 | 28 | void setVelocity(char c); 29 | 30 | 31 | // qwerty-emulating functions 32 | 33 | unsigned int qwerty_getScanCodeChar(char c); 34 | 35 | void qwerty_sendKeyDown(char c); 36 | 37 | void qwerty_sendKeyUp(char c, char location = 'm'); 38 | 39 | void qwerty_sendOutOfRangeKey(char c); 40 | 41 | void qwerty_setVelocity(char c); 42 | 43 | //void type(std::string string); 44 | -------------------------------------------------------------------------------- /src/Qwerty.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | const std::string lowNotes = "trewq0987654321"; 5 | const std::string letterNoteMap = "1!2@34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm"; 6 | const std::string highNotes = "yuiopasdfghj"; 7 | //std::string fullScale = "trewq09876543211!2@34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnmyuiopasdfghj"; 8 | 9 | const std::string velocities = "1234567890qwertyuiopasdfghjklzxc"; 10 | 11 | const int velocityList[] = { 12 | 4, 8, 12, 16, 13 | 20, 24, 28, 32, 14 | 36, 40, 44, 48, 15 | 52, 56, 60, 64, 16 | 68, 72, 76, 80, 17 | 84, 88, 92, 96, 18 | 100, 104, 108, 112, 19 | 116, 120, 124, 127 }; 20 | 21 | constexpr char findVelocity(int required) { 22 | if (required <= 4) 23 | return '1'; 24 | for (int index = 0; index < (sizeof(velocityList) / sizeof(int)) - 1; index++) { 25 | int curr = velocityList[index]; 26 | int next = velocityList[index + 1]; 27 | if ((curr <= required && next >= required) || next == 127) 28 | { 29 | return velocities[index + 1]; 30 | } 31 | } 32 | return 't'; 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ArijanJ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MIDI to Qwerty 2 | (MIDI input to Qwerty output) translator for Virtual Piano, with additional features for supported platforms (sustain, 88-keys, velocity). 3 | 4 | ### Having trouble? Visit [the wiki](https://github.com/ArijanJ/miditoqwerty/wiki/Troubleshooting). 5 | 6 | ![image_4](https://user-images.githubusercontent.com/56356662/182448262-1aaf1803-e401-4e77-9706-b7f6f4bfa4b1.png) 7 | 8 | Libraries used: 9 | - [The wonderful imgui](https://github.com/ocornut/imgui) 10 | - [shric/midi on GitHub](https://github.com/shric/midi) 11 | - [PortMidi](https://github.com/PortMidi/portmidi) 12 | 13 | Themes inspired by: 14 | [Monkeytype (check it out!)](https://github.com/monkeytypegame/monkeytype) 15 | 16 | # Building 17 | 18 | ## Prerequisites 19 | 20 | To build this project, you will need: 21 | - the `imgui` submodule, as it is; 22 | - the `gl3w` submodule, as it is; 23 | - to compile `PortMidi` with CMake; 24 | - a release of [SDL2](https://www.libsdl.org/) 25 | 26 | ### CMake 27 | 28 | | CMake field| Description| 29 | | ----------- | ----------- | 30 | | CMAKE_CONFIGURATION_TYPES| Already set (Release/Debug/...)| 31 | | CMAKE_INSTALL_PREFIX| Same as your working directory| 32 | | PORTMIDI_INCLUDE_DIR| portmidi/pm_common/| 33 | | PORTTIME_INCLUDE_DIR| portmidi/porttime/| 34 | | PORTMIDI_LIBRARY| portmidi.lib from your build| 35 | | SDL2_DIR| SDL2-x.x.xx/| 36 | 37 | Build as Release to avoid some `imgui` asserts. 38 | Remember, you still need the DLLs, /themes/ etc. from the release. 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(miditoqwerty) 3 | set(CMAKE_CXX_STANDARD 17) 4 | list( APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ) 5 | 6 | find_package(SDL2 REQUIRED) 7 | 8 | find_package(PortMidi REQUIRED) 9 | 10 | # Generate gl3w headers and sources 11 | execute_process( 12 | COMMAND cmake . 13 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gl3w 14 | ) 15 | 16 | execute_process( 17 | COMMAND cmake --build . 18 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gl3w 19 | ) 20 | 21 | set(gl3w_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/gl3w/include) 22 | 23 | add_library(gl3w ${CMAKE_CURRENT_SOURCE_DIR}/gl3w/src/gl3w.c) 24 | 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DIMGUI_IMPL_OPENGL_LOADER_GL3W -Wall") 26 | 27 | include_directories( 28 | ${gl3w_INCLUDE_DIR} 29 | ${SDL2_INCLUDE_DIRS} 30 | ${PORTMIDI_INCLUDE_DIR} 31 | ${CMAKE_SOURCE_DIR}/imgui 32 | ${CMAKE_SOURCE_DIR}/imgui/backends 33 | ${CMAKE_SOURCE_DIR}/src 34 | ) 35 | 36 | add_library (imgui 37 | imgui/backends/imgui_impl_sdl.cpp imgui/backends/imgui_impl_opengl3.cpp imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_widgets.cpp imgui/imgui_tables.cpp) 38 | 39 | add_executable(miditoqwerty 40 | WIN32 41 | src/main.cpp 42 | src/inpututils.cpp src/Log.cpp src/Midi.cpp src/Piano.cpp src/settings.cpp src/themes.cpp src/util.cpp imgui/backends/imgui_impl_sdl.cpp 43 | src/Midi.h src/themes.h src/util.h src/settings.h src/porttime.h src/resource.h src/inpututils.h src/Piano.h src/Qwerty.h src/Log.h 44 | ${CMAKE_CURRENT_SOURCE_DIR}/res/resource.rc 45 | ) 46 | target_include_directories(miditoqwerty PRIVATE src imgui imgui/backends) 47 | # target_link_libraries(miditoqwerty fmt) 48 | 49 | # For g++ < 9 50 | #target_link_libraries(miditoqwerty ${PORTMIDI_LIBRARY} ${SDL2_DIR}/lib/x64/SDL2.lib imgui stdc++fs gl3w ${CMAKE_DL_LIBS}) 51 | 52 | # For MSVC 53 | target_link_libraries(miditoqwerty ${PORTMIDI_LIBRARY} ${SDL2_DIR}/lib/x64/SDL2.lib imgui gl3w ${CMAKE_DL_LIBS}) 54 | -------------------------------------------------------------------------------- /src/Piano.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 22/4/20. 3 | // 4 | #include "imgui.h" 5 | 6 | #include "Piano.h" 7 | #include "util.h" 8 | 9 | static bool has_black(int key) { 10 | return (!((key - 1) % 7 == 0 || (key - 1) % 7 == 3) && key != 51); 11 | } 12 | 13 | void Piano::up(int key) { 14 | key_states[key] = 0; 15 | } 16 | 17 | void Piano::down(int key, int velocity) { 18 | key_states[key] = velocity; 19 | 20 | } 21 | 22 | void Piano::draw(bool *show, bool windowsEditable, ImU32 noteColor) { 23 | 24 | //ImU32 Red = IM_COL32(255,0,0,255); // piano pressed color 25 | 26 | ImU32 Red = noteColor; 27 | 28 | ImU32 Black = IM_COL32(0, 0, 0, 255); 29 | ImU32 White = IM_COL32(255, 255, 255, 255); 30 | 31 | ImGui::Begin("Keyboard", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | 32 | (!windowsEditable ? ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize : ImGuiWindowFlags_None)); 33 | 34 | ImDrawList *draw_list = ImGui::GetWindowDrawList(); 35 | ImVec2 p = ImGui::GetCursorScreenPos(); 36 | int width = 8; // editable 37 | int cur_key = 21; 38 | for (int key = 0; key < 52; key++) { 39 | ImU32 col = White; 40 | if (key_states[cur_key]) { 41 | col = Red; 42 | } 43 | draw_list->AddRectFilled( 44 | ImVec2(p.x + key * width, p.y), 45 | ImVec2(p.x + key * width + width, p.y + 60), 46 | col, 0, ImDrawCornerFlags_All); 47 | draw_list->AddRect( 48 | ImVec2(p.x + key * width, p.y), 49 | ImVec2(p.x + key * width + width, p.y + 60), 50 | Black, 0, ImDrawCornerFlags_All); 51 | cur_key++; 52 | if (has_black(key)) { 53 | cur_key++; 54 | } 55 | } 56 | cur_key = 22; 57 | for (int key = 0; key < 52; key++) { 58 | if (has_black(key)) { 59 | ImU32 col = Black; 60 | if (key_states[cur_key]) { 61 | col = Red; 62 | } 63 | draw_list->AddRectFilled( 64 | ImVec2(p.x + key * width + width * 3 / 4, p.y), 65 | ImVec2(p.x + key * width + width * 5 / 4 + 1, p.y + 40), 66 | col, 0, ImDrawCornerFlags_All); 67 | draw_list->AddRect( 68 | ImVec2(p.x + key * width + width * 3 / 4, p.y), 69 | ImVec2(p.x + key * width + width * 5 / 4 + 1, p.y + 40), 70 | Black, 0, ImDrawCornerFlags_All); 71 | 72 | cur_key += 2; 73 | } else { 74 | cur_key++; 75 | } 76 | } 77 | ImGui::End(); 78 | } 79 | 80 | std::vector Piano::current_notes() { 81 | std::vector result{}; 82 | for (int i = 0; i < 256; i++) { 83 | if (key_states[i]) { 84 | result.push_back(i); 85 | } 86 | } 87 | return result; 88 | } 89 | -------------------------------------------------------------------------------- /src/porttime.h: -------------------------------------------------------------------------------- 1 | /** @file porttime.h portable interface to millisecond timer. */ 2 | 3 | /* CHANGE LOG FOR PORTTIME 4 | 10-Jun-03 Mark Nelson & RBD 5 | boost priority of timer thread in ptlinux.c implementation 6 | */ 7 | 8 | /* Should there be a way to choose the source of time here? */ 9 | 10 | #ifdef WIN32 11 | #ifndef INT32_DEFINED 12 | // rather than having users install a special .h file for windows, 13 | // just put the required definitions inline here. portmidi.h uses 14 | // these too, so the definitions are (unfortunately) duplicated there 15 | typedef int int32_t; 16 | typedef unsigned int uint32_t; 17 | #define INT32_DEFINED 18 | #endif 19 | #else 20 | #include // needed for int32_t 21 | #endif 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #ifndef PMEXPORT 28 | #ifdef _WINDLL 29 | #define PMEXPORT __declspec(dllexport) 30 | #else 31 | #define PMEXPORT 32 | #endif 33 | #endif 34 | 35 | /** @defgroup grp_porttime PortTime: Millisecond Timer 36 | @{ 37 | */ 38 | 39 | typedef enum { 40 | ptNoError = 0, /* success */ 41 | ptHostError = -10000, /* a system-specific error occurred */ 42 | ptAlreadyStarted, /* cannot start timer because it is already started */ 43 | ptAlreadyStopped, /* cannot stop timer because it is already stopped */ 44 | ptInsufficientMemory /* memory could not be allocated */ 45 | } PtError; /**< @brief @enum PtError PortTime error code; a common return type. 46 | * No error is indicated by zero; errors are indicated by < 0. 47 | */ 48 | 49 | /** real time or time offset in milliseconds. */ 50 | typedef int32_t PtTimestamp; 51 | 52 | /** a function that gets a current time */ 53 | typedef void (PtCallback)(PtTimestamp timestamp, void *userData); 54 | 55 | /** start a real-time clock service. 56 | 57 | @param resolution the timer resolution in ms. The time will advance every 58 | \p resolution ms. 59 | 60 | @param callback a function pointer to be called every resolution ms. 61 | 62 | @param userData is passed to \p callback as a parameter. 63 | 64 | @return #ptNoError on success. See #PtError for other values. 65 | */ 66 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); 67 | 68 | /** stop the timer. 69 | 70 | @return #ptNoError on success. See #PtError for other values. 71 | */ 72 | PMEXPORT PtError Pt_Stop(void); 73 | 74 | /** test if the timer is running. 75 | 76 | @return TRUE or FALSE 77 | */ 78 | PMEXPORT int Pt_Started(void); 79 | 80 | /** get the current time in ms. 81 | 82 | @return the current time 83 | */ 84 | PMEXPORT PtTimestamp Pt_Time(void); 85 | 86 | /** pauses the current thread, allowing other threads to run. 87 | 88 | @param duration the length of the pause in ms. The true duration 89 | of the pause may be rounded to the nearest or next clock tick 90 | as determined by resolution in #Pt_Start(). 91 | */ 92 | PMEXPORT void Pt_Sleep(int32_t duration); 93 | 94 | /** @} */ 95 | 96 | #ifdef __cplusplus 97 | } 98 | #endif 99 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 25/4/20. 3 | // 4 | 5 | #include 6 | #include 7 | #include "util.h" 8 | #include "imgui.h" 9 | 10 | void advanceRainbow(int* isRed, int* isGreen, int* isBlue) { 11 | 12 | const int unit = 5; 13 | 14 | //if (ImGui::GetFrameCount() % 1 != 0) return; // this is what vsync is for dumbass 15 | if (*isGreen == 1 && *isBlue == 0) 16 | { 17 | *isRed += unit; 18 | } 19 | 20 | if (*isRed > 252 && *isBlue == 0) 21 | { 22 | *isRed = 255; 23 | *isGreen += unit; 24 | } 25 | 26 | if (*isGreen > 252 && *isBlue == 0) 27 | { 28 | *isGreen = 255; 29 | *isRed -= unit; 30 | } 31 | 32 | if (*isRed < 1 && *isGreen == 255) 33 | { 34 | *isRed = 0; 35 | *isBlue += unit; 36 | } 37 | 38 | if (*isBlue > 252 && *isRed == 0) 39 | { 40 | *isBlue = 255; 41 | *isGreen -= unit; 42 | } 43 | 44 | if (*isGreen < 1 && *isBlue == 255) 45 | { 46 | *isGreen = 0; 47 | *isRed += unit; 48 | } 49 | 50 | if (*isRed > 252 && *isGreen == 0) 51 | { 52 | *isRed = 255; 53 | *isBlue -= unit; 54 | } 55 | 56 | if (*isBlue < 1 && *isGreen == 0) 57 | { 58 | *isBlue = 0; 59 | //*isRed -= unit; 60 | if (*isRed < 1) 61 | *isGreen = 1; 62 | } 63 | /*float colors[3] = { *isRed / 255, *isGreen / 255, *isBlue / 255 }; 64 | ImGui::ColorPicker3("AAAA", colors); debug shii*/ 65 | } 66 | 67 | std::string midiNoteString(uint8_t note) { 68 | static const char *base[] = { 69 | "C", "C#", 70 | "D", "D#", 71 | "E", 72 | "F", "F#", 73 | "G", "G#", 74 | "A", "A#", 75 | "B" 76 | }; 77 | int octave = note / 12 - 1; 78 | note %= 12; 79 | char buf[50]; 80 | snprintf(buf, sizeof buf, "%s%d", base[note], octave); 81 | return std::string(buf); 82 | } 83 | 84 | std::string midiChordString(std::vector notes) { 85 | char buf[50]; 86 | if (notes.size() == 3) { 87 | if (notes[2]-notes[1] == 3 && notes[1]-notes[0] == 4) { 88 | snprintf(buf, sizeof buf, "%s major", midiNoteString(notes[0]).c_str()); 89 | return std::string(buf); 90 | } else if (notes[2]-notes[1] == 4 && notes[1]-notes[0] == 3) { 91 | snprintf(buf, sizeof buf, "%s minor", midiNoteString(notes[0]).c_str()); 92 | return std::string(buf); 93 | } 94 | } 95 | return std::string(""); 96 | } 97 | 98 | std::string timestampString(PmTimestamp timestamp) { 99 | char buf[100] = {0}; 100 | 101 | unsigned int millis = timestamp % 1000; 102 | timestamp /= 1000; 103 | 104 | unsigned int seconds = timestamp % 60; 105 | timestamp /= 60; 106 | 107 | unsigned int minutes = timestamp % 60; 108 | timestamp /= 60; 109 | 110 | unsigned int hours = timestamp % 24; 111 | timestamp /= 24; 112 | 113 | snprintf(buf, sizeof buf, "%02d:%02d:%02d.%03d", hours, minutes, seconds, millis); 114 | return std::string(buf); 115 | } 116 | -------------------------------------------------------------------------------- /src/Log.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 25/4/20. 3 | // 4 | 5 | #include "Log.h" 6 | 7 | Log::Log() { 8 | AutoScroll = true; 9 | Clear(); 10 | } 11 | 12 | void Log::Clear() { 13 | Buf.clear(); 14 | LineOffsets.clear(); 15 | LineOffsets.push_back(0); 16 | } 17 | 18 | void Log::AddLog(const char *fmt, ...) { 19 | if (!logStuff) return; 20 | int old_size = Buf.size(); 21 | va_list args; 22 | va_start(args, fmt); 23 | Buf.appendfv(fmt, args); 24 | va_end(args); 25 | for (int new_size = Buf.size(); old_size < new_size; old_size++) 26 | if (Buf[old_size] == '\n') 27 | LineOffsets.push_back(old_size + 1); 28 | } 29 | 30 | void Log::Draw(const char *title, bool *p_open) { 31 | if (!ImGui::Begin(title, NULL)) 32 | { 33 | ImGui::End(); 34 | return; 35 | } 36 | 37 | bool clear = false; 38 | // Options menu 39 | if (ImGui::BeginPopup("Options")) 40 | { 41 | ImGui::Checkbox("Auto-scroll", &AutoScroll); 42 | ImGui::Checkbox("Enable logging", (bool*) & logStuff); 43 | clear = ImGui::Button("Clear logs"); 44 | ImGui::EndPopup(); 45 | } 46 | 47 | // Main window 48 | if (ImGui::Button("Options")) 49 | ImGui::OpenPopup("Options"); 50 | ImGui::SameLine(); 51 | /*bool clear = ImGui::Button("Clear"); I HATE THIS BUTTON! DAMN YOU BUTTON! 52 | ImGui::SameLine();*/ 53 | bool copy = ImGui::Button("Copy"); 54 | ImGui::SameLine(); 55 | Filter.Draw("Filter", 100.0f);// -100.0f); i guess its fine to use pixel values 56 | 57 | ImGui::Separator(); 58 | ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); 59 | 60 | if (clear) 61 | Clear(); 62 | if (copy) 63 | ImGui::LogToClipboard(); 64 | 65 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); 66 | const char* buf = Buf.begin(); 67 | const char* buf_end = Buf.end(); 68 | if (Filter.IsActive()) 69 | { 70 | for (int line_no = 0; line_no < LineOffsets.Size; line_no++) 71 | { 72 | const char* line_start = buf + LineOffsets[line_no]; 73 | const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; 74 | if (Filter.PassFilter(line_start, line_end)) 75 | ImGui::TextUnformatted(line_start, line_end); 76 | } 77 | } 78 | else 79 | { 80 | ImGuiListClipper clipper; 81 | clipper.Begin(LineOffsets.Size); 82 | while (clipper.Step()) 83 | { 84 | for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) 85 | { 86 | const char* line_start = buf + LineOffsets[line_no]; 87 | const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; 88 | ImGui::TextUnformatted(line_start, line_end); 89 | } 90 | } 91 | clipper.End(); 92 | } 93 | ImGui::PopStyleVar(); 94 | 95 | if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) 96 | ImGui::SetScrollHereY(1.0f); 97 | 98 | ImGui::EndChild(); 99 | ImGui::End(); 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/Midi.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Chris Young on 22/4/20. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Midi.h" 9 | 10 | template 11 | void must(T err) { 12 | if (err != 0) { 13 | std::string errorText = Pm_GetErrorText(static_cast(err)); 14 | std::string errorTip = "";//NULL; 15 | 16 | if (errorText == "PortMidi: Bad pointer") 17 | errorTip = "Error: Your MIDI input port couldn't be found."; 18 | else if (errorText == "PortMidi: Host error") 19 | errorTip = "Error: Your MIDI input port is being used by another program."; 20 | else 21 | { 22 | errorTip = "Error: " + errorText; 23 | } 24 | 25 | printf("Error occurred: %s", errorTip.c_str()); 26 | MessageBox(NULL, errorTip.c_str(), errorText.c_str(), MB_OK); 27 | 28 | std::exit(1);//horrible 29 | } 30 | } 31 | 32 | PmDeviceID init() { 33 | must(Pm_Initialize()); 34 | Pt_Start(1, nullptr, nullptr); 35 | auto did = Pm_GetDefaultInputDeviceID(); 36 | if (did < 0) { 37 | std::cout << "Couldn't find any MIDI devices for input." 38 | << std::endl; 39 | return did; 40 | } 41 | int count = Pm_CountDevices(); 42 | for (int id = 0; id < count; id++) { 43 | auto di = Pm_GetDeviceInfo(id); 44 | std::cout << di->interf << "/" << di->name 45 | << ", input: " << di->input 46 | << ", output: " << di->output 47 | << ", opened: " << di->opened; 48 | if (id == did) { 49 | std::cout << " (DEFAULT)"; 50 | } 51 | std::cout << std::endl; 52 | } 53 | return did; 54 | } 55 | 56 | void Midi::InitWrapper() { 57 | stream = nullptr; 58 | must(Pm_OpenInput(&stream, deviceID, nullptr, 1024, nullptr, nullptr)); 59 | init(); 60 | } 61 | 62 | void Midi::shutdown(PortMidiStream *stream) { 63 | must(Pm_Close(stream)); 64 | must(Pt_Stop()); 65 | } 66 | 67 | 68 | Midi::Midi(PmDeviceID passedID) { 69 | deviceID = init(); // this gets default 70 | if (passedID >= 0) deviceID = passedID; // if we got passed an ID 71 | 72 | stream = nullptr; 73 | if (deviceID >= 0) { 74 | must(Pm_OpenInput(&stream, deviceID, nullptr, 1024, nullptr, nullptr)); 75 | } 76 | 77 | } 78 | 79 | void Midi::poll(std::function callback, bool debug) { 80 | PmError err = Pm_Poll(stream); 81 | if (err > 0) { 82 | int count = Pm_Read(stream, buffer, 1024); 83 | if (count > 0) { 84 | for (int ev = 0; ev < count; ev++) { 85 | PmTimestamp timestamp = buffer[ev].timestamp; 86 | PmMessage message = buffer[ev].message; 87 | uint8_t Status = ((uint32_t) message & 0xFFu); 88 | PmMessage Data1 = (((uint32_t) message >> 8) & 0xFFu); 89 | PmMessage Data2 = ((uint32_t) message >> 16 & 0xFFu); 90 | if (Data1 || Data2) { 91 | callback(timestamp, Status, Data1, Data2); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | Midi::~Midi() { 99 | shutdown(stream); 100 | } 101 | -------------------------------------------------------------------------------- /src/settings.cpp: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | std::string Setting::GetName() { 4 | return name; 5 | } 6 | 7 | Setting::Setting(std::string name, int* valueRef) { 8 | this->name = name; 9 | this->valueRef = valueRef; 10 | } 11 | 12 | void* Setting::GetValue() { 13 | //printf("Value of %s is %d\n", this->GetName().c_str(), *valueRef); 14 | return valueRef; 15 | } 16 | 17 | void Setting::SetValue(int value) { 18 | int* val = valueRef; 19 | *val = value; 20 | //printf("new value of whatever: %d\n", *val); 21 | } 22 | 23 | void SettingsHandler::AddSetting(std::string name, int* valueRef) { 24 | Setting set(name, valueRef); 25 | settings.push_back(set); 26 | } 27 | 28 | bool SettingsHandler::LoadSettings() { 29 | printf("Attempting to load settings.dat\n"); 30 | int nLine = 1; 31 | FILE* pSettingsFile ; 32 | if (fopen_s(&pSettingsFile, "settings.dat", "r") == ERROR_SUCCESS && pSettingsFile != NULL) { 33 | std::string settingName; 34 | char buf[225] = { 'a' }; 35 | while (fgets(buf, sizeof(buf), pSettingsFile)) // Next line 36 | { 37 | if (buf[strlen(buf) - 1] == '\n') 38 | buf[strlen(buf) - 1] = '\0'; // get rid of newline 39 | if (buf[0] == '!') { // custom stuff (theme & no font cause ??) 40 | if (strstr(buf, "!theme")) { 41 | printf("Found !theme\n"); 42 | char theme[128]; 43 | sscanf_s(buf, "%*s %s", theme); 44 | printf("Loading default theme %s\n", theme); 45 | LoadTheme(theme); 46 | continue; 47 | } 48 | } 49 | if (nLine % 2 == 1){ 50 | printf("Reading name: %s\n", buf); 51 | settingName = buf; 52 | printf("SETTING IS \"%s\"\n", settingName.c_str()); 53 | } 54 | else if (nLine % 2 == 0) { // every 2 lines set the setting to the setting's set setting 55 | printf("Reading value: %s\n", buf); 56 | printf("Setting %s to %d\n\n", settingName.c_str(), std::stoi(buf)); 57 | this->GetSetting(settingName)->SetValue(std::stoi(buf)); 58 | } 59 | nLine++; // fun fact i forgot this critical line 60 | } 61 | fclose(pSettingsFile); 62 | printf("Loading settings successful\n"); 63 | return true; 64 | } 65 | printf("Error occured while reading settings\n"); 66 | return false; 67 | } 68 | 69 | Setting* SettingsHandler::GetSetting(std::string match) { 70 | for(Setting& setting : settings) { 71 | std::string name = setting.GetName(); 72 | int* val; 73 | val = static_cast(setting.GetValue()); 74 | if (setting.GetName() == match) { 75 | 76 | printf("Found setting: %s, value: %d\n", match.c_str(), *val); 77 | return &setting; 78 | } 79 | } 80 | return nullptr; 81 | } 82 | 83 | int* SettingsHandler::GetValue(std::string name) { 84 | for (Setting& setting : settings) { 85 | std::string name = setting.GetName(); 86 | int* val; 87 | val = static_cast(setting.GetValue()); 88 | printf("Setting name: %s, value: %d\n", name.c_str(), *val); 89 | if (setting.GetName() == name) { 90 | return val; 91 | } 92 | } 93 | return NULL; 94 | } 95 | 96 | bool SettingsHandler::DumpSettings() { 97 | printf("Attempting to dump settings\n"); 98 | FILE* pSettingsFile; 99 | if (fopen_s(&pSettingsFile, "settings.dat", "w") == ERROR_SUCCESS && pSettingsFile != NULL) { 100 | for (Setting& setting : settings) { 101 | printf("Dumping %s\n", setting.GetName().c_str());; 102 | fprintf(pSettingsFile, "%s\n", setting.GetName().c_str()); 103 | fprintf(pSettingsFile, "%d\n", *((int*)setting.GetValue())); 104 | } 105 | 106 | const char* themeString = currentTheme.c_str(); 107 | // Save theme 108 | printf("Saving theme: %s\n", themeString); 109 | fprintf(pSettingsFile, "!theme %s\n", themeString); 110 | 111 | fclose(pSettingsFile); 112 | printf("Dumping settings successful\n"); 113 | return true; 114 | } 115 | printf("Error occured while dumping settings\n"); 116 | return false; 117 | } -------------------------------------------------------------------------------- /src/themes.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui.h" 2 | #include "themes.h" 3 | 4 | #define IM_NEWLINE "\r\n" 5 | 6 | #define IM_MIN(A, B) (((A) < (B)) ? (A) : (B)) 7 | #define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) 8 | 9 | namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } 10 | 11 | void ImGui::ShowFontSelector(const char* label) 12 | { 13 | ImGuiIO& io = ImGui::GetIO(); 14 | ImFont* font_current = ImGui::GetFont(); 15 | if (ImGui::BeginCombo(label, font_current->GetDebugName())) 16 | { 17 | for (int n = 0; n < io.Fonts->Fonts.Size; n++) 18 | { 19 | ImFont* font = io.Fonts->Fonts[n]; 20 | ImGui::PushID((void*)font); 21 | if (ImGui::Selectable(font->GetDebugName(), font == font_current)) 22 | io.FontDefault = font; 23 | ImGui::PopID(); 24 | } 25 | ImGui::EndCombo(); 26 | } 27 | } 28 | 29 | bool ImGui::ShowStyleSelector(const char* label) 30 | { 31 | static int style_idx = -1; 32 | if (ImGui::Combo(label, &style_idx, "Dark\0Light\0Classic\0")) 33 | { 34 | switch (style_idx) 35 | { 36 | case 0: ImGui::StyleColorsDark(); break; 37 | case 1: ImGui::StyleColorsLight(); break; 38 | case 2: ImGui::StyleColorsClassic(); break; 39 | } 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | 46 | bool ShowThemeSelector(const char* label, std::string& output) { 47 | 48 | static bool gotThemes = false; 49 | static std::vector themes; 50 | 51 | if (!gotThemes) { 52 | for (auto& p : std::filesystem::recursive_directory_iterator("themes")) 53 | { 54 | if (p.path().extension() == ".theme") { 55 | printf("Got theme %s\n", p.path().stem().string().c_str()); 56 | themes.push_back(p.path().stem().string()); 57 | } 58 | } 59 | gotThemes = true; 60 | } 61 | 62 | if (themes.size() == 0) return "NO_THEMES_FOUND"; 63 | 64 | static std::string current_item = currentTheme; 65 | 66 | bool retval = false; 67 | 68 | ImGui::Text("Theme"); 69 | if (ImGui::BeginCombo("##combo", current_item.c_str())) // The second parameter is the label previewed before opening the combo. 70 | { 71 | for (size_t n = 0; n < themes.size(); n++) 72 | { 73 | bool is_selected = (current_item == themes.at(n)); // You can store your selection however you want, outside or inside your objects 74 | if (ImGui::Selectable(themes.at(n).c_str(), is_selected)){ 75 | current_item = themes.at(n); 76 | output = current_item; 77 | LoadTheme("themes/" + current_item + ".theme"); 78 | retval = true; 79 | } 80 | if (is_selected) 81 | { 82 | ImGui::SetItemDefaultFocus(); // You may set the initial focus when opening the combo (scrolling + for keyboard navigation support) 83 | } 84 | } 85 | ImGui::EndCombo(); 86 | } 87 | 88 | return retval; 89 | } 90 | 91 | int LoadTheme(std::string path) { 92 | static std::string previousPath = ""; // this is sooo bad lol 93 | if (previousPath == path) return -1; 94 | 95 | // save currentTheme 96 | currentTheme = path; 97 | 98 | previousPath = path; 99 | 100 | FILE* fp; 101 | if (fopen_s(&fp, path.c_str(), "r") != 0) return -1; 102 | if (!fp) return -2; 103 | // while no eof 104 | 105 | ImGuiStyle& style = ImGui::GetStyle(); 106 | 107 | char buf[255]; 108 | int settingNo = 0; 109 | while (fgets(buf, sizeof(buf), fp)) { // Next line 110 | //sscanf_s(buf, "") 111 | float red; float green; float blue; float alpha; 112 | // fprintf_s(fp, "ImGuiCol_%s %f %f %f %f\n", name, col.x, col.y, col.z, col.w); <-- INPUT 113 | sscanf_s(buf, "%f %f %f %f %*s\n", &red, &green, &blue, &alpha); 114 | printf("Picked up ImGuiCol_%-32.32s %f %f %f %f\n", ImGui::GetStyleColorName(settingNo), red, green, blue, alpha); 115 | style.Colors[settingNo] = { red, green, blue, alpha }; 116 | settingNo++; 117 | } 118 | 119 | gBackgroundColor = style.Colors[ImGuiCol_WindowBg]; 120 | printf("gBackgroundColor set to %.2f, %.2f, %.2f, %.2f\n", gBackgroundColor.x, gBackgroundColor.y, gBackgroundColor.z, gBackgroundColor.w); 121 | gNoteColor = style.Colors[ImGuiCol_SliderGrab]; 122 | printf("gNoteColor set to %.2f, %.2f, %.2f, %.2f\n", gNoteColor.x, gNoteColor.y, gNoteColor.z, gNoteColor.w); 123 | 124 | gNoteNameColor = style.Colors[ImGuiCol_Text]; 125 | printf("midiNoteNamesColor set to %.2f, %.2f, %.2f, %.2f\n", gNoteNameColor.x, gNoteNameColor.y, gNoteNameColor.z, gNoteNameColor.w); 126 | 127 | fclose(fp); 128 | 129 | return 0; 130 | } 131 | 132 | void ImGui::ShowStyleEditor(ImGuiStyle* ref) 133 | { 134 | // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to 135 | // (without a reference style pointer, we will use one compared locally as a reference) 136 | ImGuiStyle& style = ImGui::GetStyle(); 137 | static ImGuiStyle ref_saved_style; 138 | 139 | // Default to using internal storage as reference 140 | static bool init = true; 141 | if (init && ref == NULL) 142 | ref_saved_style = style; 143 | init = false; 144 | if (ref == NULL) 145 | ref = &ref_saved_style; 146 | 147 | ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.75f); 148 | 149 | ImGui::ShowFontSelector("Fonts##Selector"); 150 | 151 | std::string curTheme; // not to be confused with currentTheme 152 | ShowThemeSelector("Theme", curTheme); 153 | ImGui::SameLine(); 154 | 155 | if (ImGui::Button("Save")) 156 | { 157 | ImGui::LogToTTY(); 158 | ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); 159 | FILE* fp; 160 | if (fopen_s(&fp, currentTheme.c_str(), "w") == 0) { 161 | for (int i = 0; i < ImGuiCol_COUNT; i++) 162 | { 163 | const ImVec4& col = style.Colors[i]; 164 | const char* name = ImGui::GetStyleColorName(i); 165 | 166 | fprintf_s(fp, "%f %f %f %f ImGuiCol_%s\n", col.x, col.y, col.z, col.w, name); 167 | } 168 | fclose(fp); 169 | } 170 | ImGui::LogFinish(); 171 | printf("Saved theme %s\n", currentTheme.c_str()); 172 | } 173 | 174 | static ImGuiTextFilter filter; 175 | filter.Draw("Filter colors", ImGui::GetFontSize() * 16); 176 | 177 | ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); 178 | ImGui::PushItemWidth(-160); 179 | for (int i = 0; i < ImGuiCol_COUNT; i++) 180 | { 181 | const char* name = ImGui::GetStyleColorName(i); 182 | if (!filter.PassFilter(name)) 183 | continue; 184 | ImGui::PushID(i); 185 | ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_NoInputs); 186 | if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) 187 | { 188 | ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } 189 | ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } 190 | } 191 | ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); 192 | ImGui::TextUnformatted(name); 193 | ImGui::PopID(); 194 | } 195 | ImGui::PopItemWidth(); 196 | ImGui::EndChild(); 197 | 198 | ImGui::PopItemWidth(); 199 | } 200 | -------------------------------------------------------------------------------- /src/inpututils.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "inpututils.h" 3 | 4 | unsigned char scanSets[2][26]; 5 | 6 | std::unordered_map scanSet1Map; 7 | std::unordered_map scanSet2Map; 8 | std::unordered_map scanSet3Map; 9 | 10 | const int SHIFT_SCAN = getScanCode(VK_SHIFT); 11 | const int SPACE_SCAN = getScanCode(VK_SPACE); 12 | const int CTRL_SCAN = getScanCode(VK_CONTROL); 13 | const int ALT_SCAN = getScanCode(VK_LMENU); 14 | 15 | // just collapse this in the ide 16 | void loadScansets() { 17 | 18 | scanSet1Map['1'] = 2; 19 | scanSet1Map['2'] = 3; 20 | scanSet1Map['3'] = 4; 21 | scanSet1Map['4'] = 5; 22 | scanSet1Map['5'] = 6; 23 | scanSet1Map['6'] = 7; 24 | scanSet1Map['7'] = 8; 25 | scanSet1Map['8'] = 9; 26 | scanSet1Map['9'] = 10; 27 | scanSet1Map['0'] = 11; 28 | scanSet1Map[' '] = SPACE_SCAN; 29 | 30 | scanSet1Map['a'] = 3; 31 | scanSet1Map['a'] = 30; 32 | scanSet1Map['b'] = 48; 33 | scanSet1Map['c'] = 46; 34 | scanSet1Map['d'] = 32; 35 | scanSet1Map['e'] = 18; 36 | scanSet1Map['f'] = 33; 37 | scanSet1Map['g'] = 34; 38 | scanSet1Map['h'] = 35; 39 | scanSet1Map['i'] = 23; 40 | scanSet1Map['j'] = 36; 41 | scanSet1Map['k'] = 37; 42 | scanSet1Map['l'] = 38; 43 | scanSet1Map['m'] = 50; 44 | scanSet1Map['n'] = 49; 45 | scanSet1Map['o'] = 24; 46 | scanSet1Map['p'] = 25; 47 | scanSet1Map['q'] = 16; 48 | scanSet1Map['r'] = 19; 49 | scanSet1Map['s'] = 31; 50 | scanSet1Map['t'] = 20; 51 | scanSet1Map['u'] = 22; 52 | scanSet1Map['v'] = 47; 53 | scanSet1Map['w'] = 17; 54 | scanSet1Map['x'] = 45; 55 | scanSet1Map['y'] = 21; 56 | scanSet1Map['z'] = 44; 57 | /* 58 | * set 1 set 2, 3 usb 59 | 02 16 16 1E !1 60 | 03 1E 1E 1F @ 2 61 | 04 26 26 20 # 3 62 | 05 25 25 21 $ 4 63 | 06 2E 2E 22 % 5 64 | 07 36 36 23 ^ 6 65 | 08 3D 3D 24 & 7 66 | 09 3E 3E 25 * 8 67 | 0A 46 46 26 (9 68 | 0B 45 45 27) 0*/ 69 | 70 | scanSet2Map['1'] = 0x16; 71 | scanSet2Map['2'] = 0x1e; 72 | scanSet2Map['3'] = 0x26; 73 | scanSet2Map['4'] = 0x25; 74 | scanSet2Map['5'] = 0x2e; 75 | scanSet2Map['6'] = 0x36; 76 | scanSet2Map['7'] = 0x3d; 77 | scanSet2Map['8'] = 0x3e; 78 | scanSet2Map['9'] = 0x46; 79 | scanSet2Map['0'] = 0x45; 80 | scanSet2Map[' '] = SPACE_SCAN; 81 | 82 | scanSet2Map['a'] = 0x1c; 83 | scanSet2Map['b'] = 0x32; 84 | scanSet2Map['c'] = 0x21; 85 | scanSet2Map['d'] = 0x23; 86 | scanSet2Map['e'] = 0x24; 87 | scanSet2Map['f'] = 0x2b; 88 | scanSet2Map['g'] = 0x34; 89 | scanSet2Map['h'] = 0x33; 90 | scanSet2Map['i'] = 0x43; 91 | scanSet2Map['j'] = 0x3b; 92 | scanSet2Map['k'] = 0x42; 93 | scanSet2Map['l'] = 0x4b; 94 | scanSet2Map['m'] = 0x3a; 95 | scanSet2Map['n'] = 0x31; 96 | scanSet2Map['o'] = 0x44; 97 | scanSet2Map['p'] = 0x4d; 98 | scanSet2Map['q'] = 0x15; 99 | scanSet2Map['r'] = 0x2d; 100 | scanSet2Map['s'] = 0x1b; 101 | scanSet2Map['t'] = 0x2c; 102 | scanSet2Map['u'] = 0x3c; 103 | scanSet2Map['v'] = 0x2a; 104 | scanSet2Map['w'] = 0x1d; 105 | scanSet2Map['x'] = 0x22; 106 | scanSet2Map['y'] = 0x35; 107 | scanSet2Map['z'] = 0x1a; 108 | 109 | scanSet3Map['1'] = 2; 110 | scanSet3Map['2'] = 3; 111 | scanSet3Map['3'] = 4; 112 | scanSet3Map['4'] = 5; 113 | scanSet3Map['5'] = 6; 114 | scanSet3Map['6'] = 7; 115 | scanSet3Map['7'] = 8; 116 | scanSet3Map['8'] = 9; 117 | scanSet3Map['9'] = 10; 118 | scanSet3Map['0'] = 11; 119 | scanSet3Map[' '] = SPACE_SCAN; 120 | 121 | scanSet3Map['a'] = 3; 122 | scanSet3Map['a'] = 30; 123 | scanSet3Map['b'] = 48; 124 | scanSet3Map['c'] = 46; 125 | scanSet3Map['d'] = 32; 126 | scanSet3Map['e'] = 18; 127 | scanSet3Map['f'] = 33; 128 | scanSet3Map['g'] = 34; 129 | scanSet3Map['h'] = 35; 130 | scanSet3Map['i'] = 23; 131 | scanSet3Map['j'] = 36; 132 | scanSet3Map['k'] = 37; 133 | scanSet3Map['l'] = 38; 134 | scanSet3Map['m'] = 50; 135 | scanSet3Map['n'] = 49; 136 | scanSet3Map['o'] = 24; 137 | scanSet3Map['p'] = 25; 138 | scanSet3Map['q'] = 16; 139 | scanSet3Map['r'] = 19; 140 | scanSet3Map['s'] = 31; 141 | scanSet3Map['t'] = 20; 142 | scanSet3Map['u'] = 22; 143 | scanSet3Map['v'] = 47; 144 | scanSet3Map['w'] = 17; 145 | scanSet3Map['x'] = 45; 146 | scanSet3Map['y'] = 44; 147 | scanSet3Map['z'] = 21; 148 | } 149 | 150 | HKL getLayout() { 151 | std::cout << GetKeyboardLayout(0) << std::endl; 152 | return GetKeyboardLayout(0); 153 | } 154 | 155 | HKL layout = getLayout(); 156 | 157 | std::string alphabet1 = "!@#$%^&*()QWERTYUIOPASDFGHJKLZXCVBNM"; 158 | std::string alphabet2 = "1234567890qwertyuiopasdfghjklzxcvbnm"; 159 | 160 | char findIndex(char c) { 161 | for (int i = 0; i < alphabet1.size(); i++) 162 | if (alphabet1[i] == c) 163 | return alphabet2[i]; 164 | return c; 165 | } 166 | 167 | unsigned int getScanCode(int code) { 168 | return MapVirtualKeyExA(code, MAPVK_VK_TO_VSC, layout); 169 | } 170 | 171 | unsigned int getScanCodeChar(char c) { 172 | return MapVirtualKeyA(VkKeyScanExA(c, layout), MAPVK_VK_TO_VSC); 173 | } 174 | 175 | void printInput(INPUT input) { 176 | char name[256]; 177 | int result = GetKeyNameTextA(input.ki.wScan << 16, name, 32); 178 | BYTE highByte = HIBYTE(input.ki.wScan); 179 | std::cout << "Input key: " << name << " \t\t| HIBYTE: " << highByte << std::endl; 180 | } 181 | 182 | // Works the same regardless of qwerty emulator 183 | void setupInput(INPUT input[], int size, int code1, int code2, bool advanced = false) { 184 | ZeroMemory(input, sizeof(input)); 185 | for (int i = 0; i < size; i++) { 186 | INPUT* ip = &input[i]; 187 | ip->type = INPUT_KEYBOARD; 188 | ip->ki.time = 0; 189 | ip->ki.dwExtraInfo = 0; 190 | 191 | if (advanced) { 192 | // Send only the key 193 | if (code1 == NULL) { 194 | ip->ki.dwFlags = (i % 2 == 0 ? KEYEVENTF_SCANCODE : KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP); 195 | ip->ki.wScan = (code2); 196 | } 197 | else { 198 | ip->ki.dwFlags = (i < 2 ? KEYEVENTF_SCANCODE : KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP); 199 | ip->ki.wScan = (i % 2 == 0 ? code1 : code2); 200 | } 201 | } 202 | //printInput(*ip); 203 | } 204 | } 205 | // 206 | void sendKeyDown(char c) { 207 | SHORT scanResult = VkKeyScanExA(c, layout); 208 | int keyCode = LOBYTE(scanResult); 209 | int shiftState = HIBYTE(scanResult); 210 | 211 | // Should not shift 212 | if (shiftState == 0) { 213 | INPUT input[1]; 214 | setupInput(input, 1, NULL, NULL); 215 | input[0].ki.dwFlags = KEYEVENTF_SCANCODE; 216 | input[0].ki.wScan = getScanCodeChar(findIndex(c)); 217 | SendInput(1, input, sizeof(INPUT)); 218 | } 219 | // Should shift 220 | else { 221 | INPUT input[3]; 222 | setupInput(input, 3, SHIFT_SCAN, getScanCodeChar(findIndex(c)), true); 223 | SendInput(3, input, sizeof(INPUT)); 224 | } 225 | /* 226 | char name[16]; 227 | GetKeyNameTextA((MapVirtualKeyExA(keyCode, MAPVK_VK_TO_VSC, layout) << 16), name, 16); 228 | */ 229 | 230 | /*std::cout << "keyCode: " << keyCode << std::endl; 231 | std::cout << "Key: " << name << std::endl; 232 | std::cout << "shiftState: " << shiftState << std::endl;*/ 233 | } 234 | 235 | void sendKeyUp(char c, char location) { 236 | INPUT input[1]; 237 | setupInput(input, 1, NULL, NULL); 238 | input[0].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; 239 | if (location == 'm') 240 | input[0].ki.wScan = getScanCodeChar(findIndex(c)); 241 | else 242 | input[0].ki.wScan = getScanCodeChar(c); 243 | SendInput(1, input, sizeof(INPUT)); 244 | } 245 | 246 | void sendOutOfRangeKey(char c) { 247 | INPUT input[3]; 248 | setupInput(input, 3, CTRL_SCAN, getScanCodeChar(findIndex(c)), true); 249 | SendInput(3, input, sizeof(INPUT)); 250 | //std::cout << "Sending out of range key " << c << std::endl; 251 | } 252 | 253 | void setVelocity(char c) { 254 | INPUT input[4]; 255 | setupInput(input, 4, ALT_SCAN, getScanCodeChar(c), true); 256 | SendInput(4, input, sizeof(INPUT)); 257 | } 258 | 259 | 260 | ///////////////////////////////////////////////// 261 | // // 262 | // QWERTY-EMULATING FUNCTIONS // 263 | // // 264 | ///////////////////////////////////////////////// 265 | 266 | 267 | // Returns the scan code in the chosen scanset 268 | unsigned int qwerty_getScanCodeChar(char c) { 269 | switch (scanSetChoice) { 270 | case 0: 271 | return scanSet1Map[c]; 272 | break; 273 | case 1: 274 | return scanSet2Map[c]; 275 | break; 276 | case 2: 277 | return scanSet3Map[c]; 278 | break; 279 | default: 280 | puts("There was an error while choosing scansets\n"); 281 | return scanSet1Map[c]; 282 | break; 283 | } 284 | } 285 | 286 | void qwerty_sendKeyDown(char c) { 287 | //SHORT scanResult = qwerty_getScanCodeChar(c); 288 | //int keyCode = LOBYTE(scanResult); 289 | //int shiftState = HIBYTE(scanResult); 290 | 291 | // Should not shift 292 | char findIndexResult = findIndex(c); 293 | if(findIndexResult == c){ 294 | INPUT input[1]; 295 | setupInput(input, 1, NULL, NULL); 296 | input[0].ki.dwFlags = KEYEVENTF_SCANCODE; 297 | input[0].ki.wScan = qwerty_getScanCodeChar(findIndexResult); 298 | SendInput(1, input, sizeof(INPUT)); 299 | } 300 | // Should shift 301 | else { 302 | INPUT input[3]; 303 | setupInput(input, 3, SHIFT_SCAN, qwerty_getScanCodeChar(findIndexResult), true); 304 | SendInput(3, input, sizeof(INPUT)); 305 | } 306 | } 307 | 308 | void qwerty_sendKeyUp(char c, char location) { 309 | INPUT input[1]; 310 | setupInput(input, 1, NULL, NULL); 311 | input[0].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; 312 | if (location == 'm') 313 | input[0].ki.wScan = qwerty_getScanCodeChar(findIndex(c)); 314 | else 315 | input[0].ki.wScan = qwerty_getScanCodeChar(c); 316 | SendInput(1, input, sizeof(INPUT)); 317 | } 318 | 319 | void qwerty_sendOutOfRangeKey(char c) { 320 | INPUT input[3]; 321 | setupInput(input, 3, CTRL_SCAN, qwerty_getScanCodeChar(findIndex(c)), true); 322 | SendInput(3, input, sizeof(INPUT)); 323 | //std::cout << "Sending out of range key " << c << std::endl; 324 | } 325 | 326 | void qwerty_setVelocity(char c) { 327 | INPUT input[4]; 328 | setupInput(input, 4, ALT_SCAN, qwerty_getScanCodeChar(c), true); 329 | //setupInput(input, 4, ALT_SCAN, getScanCodeChar(c), true); 330 | //SendInput(4, input, sizeof(INPUT)); 331 | SendInput(4, input, sizeof(INPUT)); 332 | } 333 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define SDL_MAIN_HANDLED 5 | #include 6 | //#undef WinMain // <- because SDL_main has its own main/WinMain 7 | //#undef main // <- because SDL_main has its own main/WinMain 8 | 9 | #include "imgui.h" 10 | #include "imgui_internal.h" 11 | #include "imgui_impl_sdl.h" 12 | #include "imgui_impl_opengl3.h" 13 | 14 | #include "themes.h" 15 | 16 | #include "Log.h" 17 | #include "Piano.h" 18 | #include "Midi.h" 19 | 20 | #include "util.h" 21 | #include "settings.h" 22 | #include "Qwerty.h" 23 | #include "inpututils.h" // do loadCharsets in main 24 | 25 | #include "GL/gl3w.h" // Initialize with gl3wInit() 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #define POSSIBLYEDITABLE (ImGuiWindowFlags_NoBringToFrontOnFocus | (!windowsEditable ? \ 32 | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | \ 33 | ImGuiWindowFlags_NoCollapse \ 34 | : \ 35 | ImGuiWindowFlags_None)) 36 | 37 | #define CONTROL_CHANGE (status >= 0xB0 && status <= 0xBF) 38 | #define NOTE_ON (status >= 0x90 && status <= 0x9F) 39 | #define NOTE_OFF (status >= 0x80 && status <= 0x8F) 40 | 41 | // i am absolutely mental 42 | void (*dyn_sendKeyDown)(char); //~ = sendKeyDown etc. 43 | void (*dyn_sendKeyUp)(char, char); 44 | void (*dyn_setVelocity)(char); 45 | void (*dyn_sendOutOfRangeKey)(char); 46 | 47 | // Settings 48 | 49 | // bad extern lol what am i doing 50 | int logStuff; 51 | 52 | // state 53 | bool resetting = false; 54 | 55 | bool sustainOn = false; 56 | bool rightDown = false; 57 | 58 | int scanSetChoice = 0; 59 | 60 | // saved in settings 61 | std::string defaultFont; 62 | std::string defaultTheme;// = "themes/default.theme"; 63 | 64 | // initialize externs 65 | std::string currentFont; 66 | std::string currentTheme; 67 | 68 | ImVec4 gBackgroundColor; // not settings, theme props 69 | ImVec4 gNoteColor; 70 | // removed static cause extern 71 | ImVec4 gNoteNameColor; // load this 72 | 73 | static int showTitlebar = 1; 74 | static int windowOpacity = 100; 75 | static int windowsEditable = 0; 76 | 77 | static int smallLayout = 0; 78 | 79 | static int alwaysontop = 1; 80 | 81 | static int qwertyEmulator = 1; 82 | 83 | static int enableOutput = 1; 84 | static int eightyeightkey = 1; 85 | static int sustain = 1; 86 | static int velocity = 1; 87 | 88 | int sustainCutoff = 64; // Inclusive 89 | 90 | SDL_Window* window; 91 | 92 | SettingsHandler settingsHandler; 93 | 94 | Piano piano; 95 | Midi midi; 96 | PmTimestamp lastNotePlayed = 0; 97 | 98 | Log logger; 99 | 100 | // could be worse tbh 101 | void setEmulatorFunctions() { 102 | if (qwertyEmulator == 0) { 103 | dyn_sendKeyDown = sendKeyDown; 104 | dyn_sendKeyUp = sendKeyUp; 105 | dyn_setVelocity = setVelocity; 106 | dyn_sendOutOfRangeKey = sendOutOfRangeKey; 107 | } 108 | else if (qwertyEmulator > 0) { 109 | dyn_sendKeyDown = qwerty_sendKeyDown; 110 | dyn_sendKeyUp = qwerty_sendKeyUp; 111 | dyn_setVelocity = qwerty_setVelocity; 112 | dyn_sendOutOfRangeKey = qwerty_sendOutOfRangeKey; 113 | if (qwertyEmulator == 1) 114 | scanSetChoice = 0; 115 | else if (qwertyEmulator == 2) 116 | scanSetChoice = 1; 117 | else if (qwertyEmulator == 3) 118 | scanSetChoice = 2; 119 | } 120 | 121 | // ..? 122 | 123 | printf("Routing emulator functions complete, emulator mode %d with scanset %d\n", qwertyEmulator, scanSetChoice); 124 | } 125 | 126 | void refreshSettings(){ 127 | ImGui::LoadIniSettingsFromDisk((smallLayout ? "layout_small.ini" : "layout_tall.ini")); 128 | SDL_SetWindowOpacity(window, (float)windowOpacity / 100); 129 | SDL_SetWindowBordered(window, (showTitlebar ? SDL_TRUE : SDL_FALSE)); 130 | SDL_SetWindowAlwaysOnTop(window, (alwaysontop ? SDL_TRUE : SDL_FALSE)); 131 | setEmulatorFunctions(); 132 | } 133 | 134 | void resetSettings() { 135 | 136 | alwaysontop = true; 137 | 138 | enableOutput = true; 139 | 140 | eightyeightkey = true; 141 | sustain = true; 142 | velocity = true; 143 | 144 | sustainCutoff = 64; 145 | 146 | showTitlebar = true; 147 | windowOpacity = 100; 148 | windowsEditable = false; 149 | 150 | currentTheme = "default"; 151 | defaultTheme = "themes/default.theme"; 152 | logStuff = true; 153 | LoadTheme(defaultTheme); 154 | refreshSettings(); 155 | 156 | settingsHandler.DumpSettings(); 157 | } 158 | 159 | void pollCallback(PmTimestamp timestamp, uint8_t status, PmMessage Data1, PmMessage Data2); // forward 160 | 161 | // Main code 162 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { 163 | //AllocConsole(); 164 | //ShowWindow(GetConsoleWindow(), SW_HIDE); // hide on startup 165 | FILE* stdoutNew; 166 | freopen_s(&stdoutNew, "log.txt", "w", stdout); 167 | setvbuf(stdout, NULL, _IONBF, 0); 168 | 169 | loadScansets(); // for input 170 | 171 | settingsHandler.AddSetting("Always on top", &alwaysontop); 172 | settingsHandler.AddSetting("Editable windows", &windowsEditable); 173 | settingsHandler.AddSetting("Show titlebar", &showTitlebar); 174 | settingsHandler.AddSetting("Window opacity", &windowOpacity); 175 | settingsHandler.AddSetting("Small layout", &smallLayout); 176 | settingsHandler.AddSetting("Enable output", &enableOutput); 177 | settingsHandler.AddSetting("88-key support", &eightyeightkey); 178 | settingsHandler.AddSetting("Sustain", &sustain); 179 | settingsHandler.AddSetting("Velocity", &velocity); 180 | settingsHandler.AddSetting("Sustain cutoff", &sustainCutoff); 181 | settingsHandler.AddSetting("Log stuff", &logStuff); 182 | settingsHandler.AddSetting("QWERTY emulation", &qwertyEmulator); 183 | 184 | // before the midithread (wow this works great) 185 | if (midi.deviceID < 0) { 186 | std::exit(2); 187 | } 188 | 189 | fflush(stdoutNew); 190 | 191 | // Setup SDL 192 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { 193 | printf("Error: %s\n", SDL_GetError()); 194 | return -1; 195 | } 196 | // GL 3.0 + GLSL 130 197 | const char* glsl_version = "#version 130"; 198 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); 199 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 200 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 201 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 202 | 203 | // Create window with graphics context 204 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 205 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 206 | SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); 207 | SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | 208 | SDL_WINDOW_ALLOW_HIGHDPI | 209 | SDL_WINDOW_ALWAYS_ON_TOP ); 210 | window = SDL_CreateWindow("Midi to Qwerty", SDL_WINDOWPOS_CENTERED, 211 | SDL_WINDOWPOS_CENTERED, 435, 550, window_flags); 212 | //SDL_SetWindowHitTest(window, DragCallback, 0); - old way of dragging with ctrl 213 | SDL_GLContext gl_context = SDL_GL_CreateContext(window); 214 | SDL_GL_MakeCurrent(window, gl_context); 215 | SDL_GL_SetSwapInterval(1); // Enable vsync 216 | 217 | // Initialize OpenGL loader 218 | bool err = gl3wInit() != 0; 219 | if (err) { 220 | fprintf(stderr, "Failed to initialize OpenGL loader!\n"); 221 | return 1; 222 | } 223 | 224 | // Setup Dear ImGui context 225 | IMGUI_CHECKVERSION(); 226 | ImGui::CreateContext(); 227 | ImGuiIO& io = ImGui::GetIO(); 228 | io.IniFilename = NULL; // Manual setting with LoadIniFileXXX 229 | ImGui::LoadIniSettingsFromDisk((smallLayout?"layout_small.ini" : "layout_tall.ini")); 230 | 231 | std::string initializedFont; 232 | 233 | fflush(stdoutNew); 234 | 235 | // Load fonts 236 | for (auto& p : std::filesystem::recursive_directory_iterator("fonts")) // Add the rest of the fonts in fonts/ 237 | { 238 | if (p.path().extension() == ".ttf") { 239 | 240 | std::string relativePath = p.path().stem().string(); 241 | if (relativePath == defaultFont) continue; // Dont load default font, already loaded 242 | 243 | std::string fullPath = "fonts/" + relativePath + ".ttf"; 244 | 245 | if (fullPath == initializedFont) continue; 246 | 247 | printf("Font found: %s\n", fullPath.c_str()); 248 | 249 | ImGui::GetIO().Fonts->AddFontFromFileTTF(fullPath.c_str(), 13); 250 | } 251 | } 252 | 253 | fflush(stdoutNew); 254 | 255 | (void) io; 256 | 257 | // Setup Dear ImGui style 258 | ImGui::StyleColorsDark(); 259 | ImGui::GetStyle().WindowTitleAlign = ImVec2(0.5f, 0.5f); 260 | ImGui::GetStyle().WindowRounding = 8.0f; 261 | ImGui::GetStyle().FrameRounding = 4.0f; 262 | ImGui::GetStyle().GrabRounding = 4.0f; 263 | 264 | // Setup Platform/Renderer bindings 265 | ImGui_ImplSDL2_InitForOpenGL(window, gl_context); 266 | ImGui_ImplOpenGL3_Init(glsl_version); 267 | 268 | // if couldnt load settings 269 | if (!settingsHandler.LoadSettings()) { 270 | printf("Could not load settings - resetting them\n"); 271 | resetSettings(); 272 | settingsHandler.DumpSettings(); 273 | } 274 | 275 | if (smallLayout) { 276 | SDL_SetWindowSize(window, 435, 315); 277 | printf("Loaded small layout\n"); 278 | } 279 | else { 280 | SDL_SetWindowSize(window, 435, 550); 281 | printf("Loaded tall layout\n"); 282 | } 283 | 284 | // Some settings need to be applied before the main loop because they rely on immediate mode paradigm 285 | refreshSettings(); 286 | 287 | // Our state 288 | bool show_midi_window = true; 289 | bool show_piano_window = true; 290 | bool show_log_window = true; 291 | bool show_settings = true; 292 | bool rainbowMode = false; 293 | 294 | // Set up thread 295 | auto thr = [](std::future futureObj) { 296 | while (futureObj.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout) { 297 | midi.poll(pollCallback, true); 298 | } 299 | printf("Finished MIDI thread\n"); 300 | }; 301 | 302 | std::promise midiThreadExitSignal; 303 | std::future futureObj = midiThreadExitSignal.get_future(); 304 | std::thread midithread(thr, std::move(futureObj)); 305 | 306 | // Main loop 307 | bool done = false; 308 | while (!done) { 309 | SDL_Event event; 310 | while (SDL_PollEvent(&event)) { 311 | ImGui_ImplSDL2_ProcessEvent(&event); 312 | if (event.type == SDL_QUIT) 313 | done = true; 314 | if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && 315 | event.window.windowID == SDL_GetWindowID(window)) 316 | done = true; 317 | if (event.type == SDL_KEYDOWN) { 318 | if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) 319 | done = true; 320 | } 321 | 322 | if (event.type == SDL_MOUSEBUTTONDOWN) { 323 | if (event.button.button == SDL_BUTTON_RIGHT) { 324 | rightDown = true; 325 | } 326 | } 327 | if (event.type == SDL_MOUSEBUTTONUP) { 328 | if (event.button.button == SDL_BUTTON_RIGHT) { 329 | rightDown = false; 330 | } 331 | } 332 | } 333 | 334 | // Start the Dear ImGui frame 335 | ImGui_ImplOpenGL3_NewFrame(); 336 | ImGui_ImplSDL2_NewFrame(window); 337 | ImGui::NewFrame(); 338 | if (show_midi_window) { 339 | ImGui::Begin("Midi", NULL, POSSIBLYEDITABLE); 340 | std::ostringstream os; 341 | auto current_notes = piano.current_notes(); 342 | for (auto ¬e : current_notes) { 343 | os << midiNoteString(note) << " "; 344 | } 345 | std::string playing = os.str(); 346 | ImGui::Text("Playing: "); 347 | ImGui::SameLine(); 348 | if (!playing.empty()) { 349 | ImGui::TextColored(gNoteNameColor, "%s", playing.c_str()); 350 | } else { 351 | ImGui::Text(""); 352 | } 353 | ImGui::Text("Qwerty: "); 354 | ImGui::SameLine(); 355 | if (!playing.empty()) { 356 | for (auto& note : current_notes) { 357 | //logger.AddLog("NOTE %c", note); 358 | //ImGui::TextColored(ImVec4(0, 1, 0, 1), "%d", note); 359 | if (note > 0 && note < 36) { 360 | ImGui::TextColored(ImVec4(1, 1, 0, 1), "%c ", lowNotes.c_str()[abs(note - 35)]); 361 | } 362 | // Okay 363 | else if (note >= 36 && note <= 96) { 364 | ImGui::TextColored(ImVec4(0, 0, 1, 1), "%c ", letterNoteMap.c_str()[note - 36]);; 365 | } 366 | // High 367 | else if (note > 96 && note < 122) { 368 | ImGui::TextColored(ImVec4(1, 0, 0, 1), "%c ", highNotes.c_str()[note - 97]); 369 | } 370 | else { 371 | logger.AddLog("Could not find key %d\n", note); 372 | } 373 | ImGui::SameLine(); 374 | } 375 | 376 | } else { ImGui::Text(""); } 377 | ImGui::End(); 378 | } 379 | 380 | if (rainbowMode) { /*Do all the rainbow stuff in one block*/ 381 | static int r = 0; static int g = 0; static int b = 0; 382 | 383 | ImVec4 normalizedRainbow = ImVec4((float)r / 255, (float)g / 255, (float)b / 255, 1.0f); 384 | 385 | advanceRainbow(&r, &b, &g); 386 | 387 | gBackgroundColor = normalizedRainbow; 388 | gNoteColor = normalizedRainbow; 389 | gNoteNameColor = normalizedRainbow; 390 | } 391 | 392 | if (show_piano_window) { 393 | piano.draw(&show_piano_window, windowsEditable, 394 | IM_COL32(gNoteColor.x * 255, gNoteColor.y * 255, gNoteColor.z * 255, 255)); 395 | } 396 | 397 | if (true) { 398 | ImGui::Begin("Settings", NULL, POSSIBLYEDITABLE); 399 | 400 | ImGui::Text("Window settings"); 401 | if (ImGui::Checkbox("Always on top", (bool*)&alwaysontop)) 402 | SDL_SetWindowAlwaysOnTop(window, (SDL_bool)alwaysontop); 403 | ImGui::Checkbox("Editable windows", (bool*)& windowsEditable); 404 | if (ImGui::Checkbox("Show titlebar", (bool*)&showTitlebar)) { 405 | // do below SDL_SetWindowOpacity(window, 0.0f); 406 | SDL_SetWindowBordered(window, (SDL_bool)showTitlebar); 407 | } 408 | 409 | ImGui::Text("Opacity"); 410 | if (ImGui::SliderInt("##", &windowOpacity, 10, 100, "%d%%")) { 411 | logger.AddLog("Setting opacity to %d\n", windowOpacity); 412 | SDL_SetWindowOpacity(window, (float)windowOpacity / 100); 413 | } 414 | 415 | static bool showStyleEditor = false; 416 | if (ImGui::Button((!showStyleEditor ? "Open theme editor" : "Close theme editor"))) 417 | showStyleEditor = !showStyleEditor; 418 | 419 | const char* layouts[] = { "Small", "Tall" }; 420 | static const char* current_item = (smallLayout?"Small":"Tall"); 421 | 422 | ImGui::Text("Layout"); 423 | ImGui::PushItemWidth(ImGui::GetFontSize() * 6); // 6 chars - Small, Tall = 5, 4 424 | if (ImGui::BeginCombo("##combo", current_item)) // The second parameter is the label previewed before opening the combo. 425 | { 426 | for (int n = 0; n < IM_ARRAYSIZE(layouts); n++) 427 | { 428 | bool is_selected = (current_item == layouts[n]); // You can store your selection however you want, outside or inside your objects 429 | if (ImGui::Selectable(layouts[n], is_selected)) { 430 | current_item = layouts[n]; 431 | if (current_item == "Small") { 432 | SDL_SetWindowSize(window, 435, 310); 433 | ImGui::LoadIniSettingsFromDisk("layout_small.ini"); 434 | smallLayout = true; 435 | } 436 | else if (current_item == "Tall") { 437 | SDL_SetWindowSize(window, 435, 550); 438 | ImGui::LoadIniSettingsFromDisk("layout_tall.ini"); 439 | smallLayout = false; 440 | } 441 | } 442 | if (is_selected) 443 | ImGui::SetItemDefaultFocus(); // You may set the initial focus when opening the combo (scrolling + for keyboard navigation support) 444 | } 445 | ImGui::EndCombo(); 446 | } 447 | ImGui::PopItemWidth(); 448 | 449 | if (showStyleEditor) { 450 | ImGui::Begin("Theme Editor"); 451 | 452 | ImGui::ShowStyleEditor(); 453 | ImGui::End(); 454 | } 455 | 456 | ImGui::ColorEdit3("Background color", (float*)&gBackgroundColor, ImGuiColorEditFlags_NoInputs); 457 | ImGui::ColorEdit3("Note color", (float*)&gNoteColor, ImGuiColorEditFlags_NoInputs); 458 | 459 | ImGui::Checkbox("RAINBOW MODE!", &rainbowMode); 460 | 461 | ImGui::Text("Piano settings"); 462 | 463 | if (ImGui::Checkbox("Enable output", (bool*)&enableOutput)) { 464 | // Clear all notes 465 | for (int i = 21; i <= 108; i++) 466 | piano.up(i); 467 | } 468 | 469 | ImGui::Checkbox("88-key support", (bool*)& eightyeightkey); 470 | 471 | ImGui::Checkbox("Sustain", (bool*)& sustain); 472 | 473 | ImGui::Text("Sustain cutoff"); 474 | ImGui::SliderInt("", &sustainCutoff, 0, 127); 475 | if (ImGui::IsItemHovered()) { 476 | ImGui::SetTooltip("CTRL + Click to enter a value,\ndefault is 64"); 477 | } 478 | 479 | ImGui::Checkbox("Velocity", (bool*)& velocity); 480 | 481 | static bool foundDevice = false; 482 | auto did = Pm_GetDefaultInputDeviceID(); 483 | if (did < 0) { 484 | std::cout << "Couldn't find any MIDI devices for input." 485 | << std::endl; 486 | return did; 487 | } 488 | else if(!foundDevice) { 489 | logger.AddLog("Opened MIDI device %s\n", Pm_GetDeviceInfo(did)->name); 490 | foundDevice = true; 491 | } 492 | 493 | ImGui::Text("QWERTY Emulator"); 494 | static const char* qwertyEmulatorMode; 495 | static bool didEmuTextInit = false; 496 | if (!didEmuTextInit) { 497 | if (qwertyEmulator == 0) qwertyEmulatorMode = "Off"; 498 | if (qwertyEmulator == 1) qwertyEmulatorMode = "Set 1"; 499 | if (qwertyEmulator == 2) qwertyEmulatorMode = "Set 2"; 500 | didEmuTextInit = true; 501 | } // these save 502 | 503 | if (ImGui::BeginCombo("Mode", qwertyEmulatorMode)) { 504 | if (ImGui::Selectable("Off")) { 505 | qwertyEmulatorMode = "Off"; 506 | qwertyEmulator = 0; 507 | setEmulatorFunctions(); 508 | } 509 | if (ImGui::IsItemHovered()) 510 | ImGui::SetTooltip("Consults Windows and your keyboard layout,\nuseful for playing outside of Roblox"); 511 | 512 | if (ImGui::Selectable("Set 1")) { 513 | qwertyEmulatorMode = "Set 1"; 514 | qwertyEmulator = 1; 515 | setEmulatorFunctions(); 516 | } 517 | if (ImGui::IsItemHovered()) 518 | ImGui::SetTooltip("This should be your go-to setting for Roblox"); 519 | 520 | if (ImGui::Selectable("Set 2")) { 521 | qwertyEmulatorMode = "Set 2"; 522 | qwertyEmulator = 2; 523 | setEmulatorFunctions(); 524 | } 525 | if (ImGui::IsItemHovered()) 526 | ImGui::SetTooltip("Use this if your keyboard doesn't\nproperly support Set 1"); 527 | 528 | if (ImGui::Selectable("QWERTZ")) { 529 | qwertyEmulatorMode = "QWERTZ"; 530 | qwertyEmulator = 3; 531 | setEmulatorFunctions(); 532 | } 533 | if (ImGui::IsItemHovered()) 534 | ImGui::SetTooltip("This is like Set 1, but swaps Y with Z"); 535 | 536 | ImGui::EndCombo(); 537 | } 538 | if (ImGui::IsItemHovered()) { 539 | ImGui::SetTooltip("Allows you to play on alternate keyboard layouts,\n should be faster in practice for normal use"); 540 | } 541 | 542 | ImGui::Text("MIDI Input"); 543 | PmDeviceID selectedDevice = did; 544 | static const char* selectedDeviceName = Pm_GetDeviceInfo(did)->name; 545 | if (ImGui::BeginCombo("Port", selectedDeviceName)) { 546 | for (int i = 0; i < Pm_CountDevices() - 1; i++) { 547 | const PmDeviceInfo* deviceInfo = Pm_GetDeviceInfo(i); 548 | if (deviceInfo->input == 0) continue; 549 | bool is_selected = (selectedDeviceName == Pm_GetDeviceInfo(selectedDevice)->name); 550 | 551 | if (ImGui::Selectable(deviceInfo->name, is_selected)) { 552 | std::cout << "Changed to " << deviceInfo->name << '\n'; 553 | selectedDeviceName = deviceInfo->name; 554 | midi.shutdown(midi.stream); 555 | midi.deviceID = i; 556 | midi.InitWrapper(); // ......... if it works it works right? 557 | logger.AddLog("Opened MIDI device %s\n", selectedDeviceName); 558 | } 559 | 560 | if (is_selected) 561 | ImGui::SetItemDefaultFocus(); 562 | } 563 | ImGui::EndCombo(); 564 | } 565 | 566 | ImGui::Text("Reset"); 567 | 568 | if (ImGui::Button("Reset settings")) { 569 | resetting = true; 570 | } 571 | 572 | if (resetting) { 573 | ImGui::PushItemWidth(ImGui::GetFontSize() * 7.0f); 574 | ImGui::SameLine(); 575 | if (ImGui::Button("Yes")) { 576 | printf("Resetting settings\n"); 577 | resetSettings(); 578 | resetting = false; 579 | } 580 | ImGui::SameLine(); 581 | if (ImGui::Button("No...")) 582 | resetting = false; 583 | } 584 | 585 | ImGui::End(); 586 | } 587 | 588 | if (true) { // LOG(ger) WINDOW 589 | ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); 590 | //ImGui::PushItemWidth(ImGui::GetFontSize() * 10); not here 591 | ImGui::Begin("Log", NULL, POSSIBLYEDITABLE); 592 | //ImGui::PopItemWidth(); 593 | ImGui::End(); 594 | 595 | // Actually call in the regular Log helper (which will Begin() into the same window as we just did) 596 | logger.Draw("Log", &show_log_window); 597 | } 598 | 599 | static bool firstDown = true; 600 | static int relOldMousePos[2]; 601 | if (rightDown) { 602 | int x, y; 603 | int winx, winy; 604 | int sizex, sizey; 605 | SDL_GetWindowPosition(window, &winx, &winy); 606 | SDL_GetWindowSize(window, &sizex, &sizey); 607 | SDL_GetMouseState(&x, &y); 608 | x -= sizex / 2; 609 | y -= sizey / 2; 610 | if (firstDown) { 611 | SDL_GetMouseState(&relOldMousePos[0], &relOldMousePos[1]); 612 | firstDown = false; 613 | } 614 | //SDL_SetRelativeMouseMode(SDL_TRUE); 615 | SDL_SetWindowPosition(window, winx + x, winy + y); 616 | } 617 | 618 | // Limit the FPS to 100 619 | // TODO: Rewrite this entire trainwreck of a project 620 | SDL_Delay(10); 621 | 622 | // Rendering 623 | ImGui::Render(); 624 | glViewport(0, 0, (int) io.DisplaySize.x, (int) io.DisplaySize.y); 625 | glClearColor(gBackgroundColor.x, gBackgroundColor.y, gBackgroundColor.z, gBackgroundColor.w); // w was here 626 | glClear(GL_COLOR_BUFFER_BIT); 627 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 628 | SDL_GL_SwapWindow(window); 629 | } 630 | 631 | settingsHandler.DumpSettings(); 632 | 633 | // Cleanup 634 | midiThreadExitSignal.set_value();; 635 | midithread.join(); // Wait for kil 636 | //std::this_thread::sleep_for(std::chrono::milliseconds(100)); //safety net 637 | ImGui_ImplOpenGL3_Shutdown(); 638 | ImGui_ImplSDL2_Shutdown(); 639 | ImGui::DestroyContext(); 640 | 641 | SDL_GL_DeleteContext(gl_context); 642 | SDL_DestroyWindow(window); 643 | SDL_Quit(); 644 | 645 | return 0; 646 | } 647 | 648 | void pollCallback(PmTimestamp timestamp, uint8_t status, PmMessage Data1, PmMessage Data2) { 649 | logger.AddLog("Event status: %d, Data1: %04X, Data2: %04X\n", status, Data1, Data2); 650 | 651 | if (!enableOutput) { 652 | if (NOTE_ON) 653 | piano.down(Data1, Data2); 654 | else if (NOTE_OFF) { 655 | piano.up(Data1); 656 | } 657 | return; 658 | } 659 | 660 | char desiredKey = 'x'; 661 | char keyLocation = 'm'; 662 | 663 | if (NOTE_ON || NOTE_OFF) { 664 | 665 | if (Data1 > 0 && Data1 < 36) { 666 | desiredKey = lowNotes[abs((int)Data1 - 35)]; 667 | keyLocation = 'l'; 668 | if (!eightyeightkey) { 669 | logger.AddLog("Low note %c skipped\n", desiredKey); 670 | return; 671 | } 672 | } 673 | // Okay 674 | else if (Data1 >= 36 && Data1 <= 96) { 675 | desiredKey = letterNoteMap[(int)Data1 - 36]; 676 | keyLocation = 'm'; 677 | } 678 | // High 679 | else if (Data1 > 96 && Data1 < 122) { 680 | desiredKey = highNotes[abs((int)Data1 - 97)]; 681 | keyLocation = 'h'; 682 | if (!eightyeightkey) { 683 | logger.AddLog("High note %c skipped\n", desiredKey); 684 | return; 685 | } 686 | } 687 | else { 688 | logger.AddLog("Could not find key %d\n", Data1); 689 | } 690 | } 691 | 692 | if CONTROL_CHANGE{ // http://midi.teragonaudio.com/tech/midispec/ctllist.htm - Control Change 693 | logger.AddLog("Control change: [1]: %04X [2]: %04X\n", Data1, Data2); 694 | if (Data1 == 0x40) { // http://midi.teragonaudio.com/tech/midispec/hold.htm - Sustain Pedal 695 | if (!sustain) { 696 | logger.AddLog("Skipping sustain control\n"); 697 | return; 698 | } 699 | if (Data2 >= sustainCutoff && !sustainOn) { 700 | std::async(std::launch::async, dyn_sendKeyDown, ' ');// dyn_sendKeyDown(' '); 701 | sustainOn = true; 702 | logger.AddLog("Sustain down"); 703 | } 704 | else if (Data2 < sustainCutoff && sustainOn) { 705 | std::async(std::launch::async, dyn_sendKeyUp, ' ', 'm');//dyn_sendKeyUp(' ', 'm'); 706 | sustainOn = false; 707 | logger.AddLog("Sustain up"); 708 | } 709 | return; 710 | } 711 | } 712 | 713 | if NOTE_ON{ // NoteOn 714 | piano.down(Data1, Data2); 715 | 716 | if (Data2 == 0) { 717 | std::async(std::launch::async, dyn_sendKeyUp, desiredKey, keyLocation);//dyn_sendKeyUp(desiredKey, keyLocation); 718 | return; 719 | } 720 | 721 | if (velocity == true) { 722 | static char prevVelocity = 'X'; // init to somebs 723 | char velocity = findVelocity(Data2); 724 | if (prevVelocity == velocity) { 725 | logger.AddLog("Same velocity, skipping "); 726 | } 727 | logger.AddLog("Velocity: %c\n", velocity); 728 | std::async(std::launch::async, dyn_setVelocity, velocity);//dyn_setVelocity(velocity); 729 | prevVelocity = velocity; 730 | } 731 | else { 732 | logger.AddLog("Skipping velocity: off\n"); 733 | } 734 | 735 | if (keyLocation == 'm') 736 | { 737 | std::async(std::launch::async, dyn_sendKeyUp, desiredKey, 'm');//dyn_sendKeyUp(desiredKey, 'm'); // last ditch effort? 738 | std::async(std::launch::async, dyn_sendKeyDown, desiredKey);//dyn_sendKeyDown(desiredKey); 739 | } 740 | else { 741 | std::async(std::launch::async, dyn_sendOutOfRangeKey, desiredKey);//dyn_sendOutOfRangeKey(desiredKey); 742 | } 743 | 744 | logger.AddLog("Note %c, location: %c\n", desiredKey, keyLocation); 745 | return; 746 | 747 | } 748 | if NOTE_OFF{ // NoteOff 749 | piano.up(Data1); 750 | 751 | logger.AddLog("Releasing %c\n", desiredKey); 752 | std::async(std::launch::async, dyn_sendKeyUp, desiredKey, keyLocation);//dyn_sendKeyUp(desiredKey, keyLocation); 753 | return; 754 | } 755 | logger.AddLog("%s: status: %x, %d, %d\n", timestampString(timestamp).c_str(), status, Data1, Data2); 756 | } 757 | 758 | --------------------------------------------------------------------------------