diff --git a/src/config.h b/src/config.h index f72b579..b7d58a0 100644 --- a/src/config.h +++ b/src/config.h @@ -1,5 +1,7 @@ #pragma once + + // Keyboard layers. // These must have the same indices as // your layers in QMK. @@ -26,6 +28,8 @@ enum layer_layout_ids { // Prevents absurd cpu usage. #define LOOP_SLEEP_MS 20 + +//#define DISABLE_SPELL #define HUNSPELL_AFF_EN "/usr/share/hunspell/en_US.aff" #define HUNSPELL_DIC_EN "/usr/share/hunspell/en_US.dic" @@ -35,6 +39,8 @@ enum layer_layout_ids { #define HID_USAGE 0x61 #define HID_USAGE_PAGE 0xFF60 +#define DISABLE_VISUALIZER + // USB packet size, in bytes. // Usually 32, but depends on keyboard. #define RAW_EPSIZE 32 diff --git a/src/ergodox.hpp b/src/ergodox.hpp index d8ee91f..f7e73e0 100644 --- a/src/ergodox.hpp +++ b/src/ergodox.hpp @@ -21,7 +21,7 @@ hid_init() and hid_exit(). class Ergodox { public: - // USB Device paramaters + // USB Device parameters const unsigned short vendor_id; const unsigned short product_id; const unsigned short usage; diff --git a/src/main.cpp b/src/main.cpp index 1f1ded8..31cf31c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,26 +3,34 @@ #include #include #include + // For reading FIFO #include #include + // For sleep -#include #include -// MPD client -#include "mpd/client.h" -// Spell checking -#include "hunspell.hxx" + // Local files -#include "utility/bitmap.hpp" -#include "utility/buffer.hpp" -#include "signal_processing/fft.hpp" #include "ergodox.hpp" #include "commands.h" #include "config.h" + #include "spdlog/spdlog.h" +#ifndef DISABLE_SPECIAL_CHAR +#include "modules/special_chars.hpp" +#endif + +#ifndef DISABLE_SPELL +#include "modules/spell.hpp" +#endif + +#ifndef DISABLE_VISUALIZER +#include "modules/visualizer.hpp" +#endif + // TODO: // // MPD connection error handling @@ -51,392 +59,39 @@ // pcm from pulse -const size_t width = 10; -const size_t height = BOTTOM_SKIP + KB_RESOLUTION + TOP_SKIP; -enum hid_keyboard_keypad_usage { - KC_NO = 0x00, - KC_ROLL_OVER, - KC_POST_FAIL, - KC_UNDEFINED, - KC_A, - KC_B, - KC_C, - KC_D, - KC_E, - KC_F, - KC_G, - KC_H, - KC_I, - KC_J, - KC_K, - KC_L, - KC_M, // 0x10 - KC_N, - KC_O, - KC_P, - KC_Q, - KC_R, - KC_S, - KC_T, - KC_U, - KC_V, - KC_W, - KC_X, - KC_Y, - KC_Z, - KC_1, - KC_2, - KC_3, // 0x20 - KC_4, - KC_5, - KC_6, - KC_7, - KC_8, - KC_9, - KC_0, - KC_ENTER, - KC_ESCAPE, - KC_BACKSPACE, - KC_TAB, - KC_SPACE, - KC_MINUS, - KC_EQUAL, - KC_LEFT_BRACKET, - KC_RIGHT_BRACKET, // 0x30 - KC_BACKSLASH, - KC_NONUS_HASH, - KC_SEMICOLON, - KC_QUOTE, - KC_GRAVE, - KC_COMMA, - KC_DOT, - KC_SLASH, - KC_CAPS_LOCK, - KC_F1, - KC_F2, - KC_F3, - KC_F4, - KC_F5, - KC_F6, - KC_F7, // 0x40 - KC_F8, - KC_F9, - KC_F10, - KC_F11, - KC_F12, - KC_PRINT_SCREEN, - KC_SCROLL_LOCK, - KC_PAUSE, - KC_INSERT, - KC_HOME, - KC_PAGE_UP, - KC_DELETE, - KC_END, - KC_PAGE_DOWN, - KC_RIGHT, - KC_LEFT, // 0x50 - KC_DOWN, - KC_UP, - KC_NUM_LOCK, - KC_KP_SLASH, - KC_KP_ASTERISK, - KC_KP_MINUS, - KC_KP_PLUS, - KC_KP_ENTER, - KC_KP_1, - KC_KP_2, - KC_KP_3, - KC_KP_4, - KC_KP_5, - KC_KP_6, - KC_KP_7, - KC_KP_8, // 0x60 - KC_KP_9, - KC_KP_0, - KC_KP_DOT, - KC_NONUS_BACKSLASH, - KC_APPLICATION, - KC_KB_POWER, - KC_KP_EQUAL, - KC_F13, - KC_F14, - KC_F15, - KC_F16, - KC_F17, - KC_F18, - KC_F19, - KC_F20, - KC_F21, // 0x70 - KC_F22, - KC_F23, - KC_F24, - KC_EXECUTE, - KC_HELP, - KC_MENU, - KC_SELECT, - KC_STOP, - KC_AGAIN, - KC_UNDO, - KC_CUT, - KC_COPY, - KC_PASTE, - KC_FIND, - KC_KB_MUTE, - KC_KB_VOLUME_UP, // 0x80 - KC_KB_VOLUME_DOWN, - KC_LOCKING_CAPS_LOCK, - KC_LOCKING_NUM_LOCK, - KC_LOCKING_SCROLL_LOCK, - KC_KP_COMMA, - KC_KP_EQUAL_AS400, - KC_INTERNATIONAL_1, - KC_INTERNATIONAL_2, - KC_INTERNATIONAL_3, - KC_INTERNATIONAL_4, - KC_INTERNATIONAL_5, - KC_INTERNATIONAL_6, - KC_INTERNATIONAL_7, - KC_INTERNATIONAL_8, - KC_INTERNATIONAL_9, - KC_LANGUAGE_1, // 0x90 - KC_LANGUAGE_2, - KC_LANGUAGE_3, - KC_LANGUAGE_4, - KC_LANGUAGE_5, - KC_LANGUAGE_6, - KC_LANGUAGE_7, - KC_LANGUAGE_8, - KC_LANGUAGE_9, - KC_ALTERNATE_ERASE, - KC_SYSTEM_REQUEST, - KC_CANCEL, - KC_CLEAR, - KC_PRIOR, - KC_RETURN, - KC_SEPARATOR, - KC_OUT, // 0xA0 - KC_OPER, - KC_CLEAR_AGAIN, - KC_CRSEL, - KC_EXSEL, -}; - -// Replacement chars for now, since wide strings are hard. -char ru_kc_to_char(uint8_t keycode) { - switch (keycode) { - case KC_GRAVE: - return '~';//L"ё"; - case KC_1: - return '1';//L"1"; - case KC_2: - return '2';//L"2"; - case KC_3: - return '3';//L"3"; - case KC_4: - return '4';//L"4"; - case KC_5: - return '5';//L"5"; - case KC_6: - return '6';//L"6"; - case KC_7: - return '7';//L"7"; - case KC_8: - return '8';//L"8"; - case KC_9: - return '9';//L"9"; - case KC_0: - return '0';//L"0"; - case KC_Q: - return '^';//L"й"; - case KC_W: - return '*';//L"ц"; - case KC_E: - return 'y';//L"у"; - case KC_R: - return 'k';//L"к"; - case KC_T: - return 'e';//L"е"; - case KC_Y: - return 'H';//L"н"; - case KC_U: - return 'g';//L"г"; - case KC_I: - return 'w';//L"ш"; - case KC_O: - return 'W';//L"щ"; - case KC_P: - return 'z';//L"з"; - case KC_LEFT_BRACKET: - return 'x';//L"х"; - case KC_RIGHT_BRACKET: - return '!';//L"ъ"; - case KC_A: - return 'f';//L"ф"; - case KC_S: - return 'i';//L"ы"; - case KC_D: - return 'B';//L"в"; - case KC_F: - return 'a';//L"а"; - case KC_G: - return 'n';//L"п"; - case KC_H: - return 'p';//L"р"; - case KC_J: - return 'o';//L"о"; - case KC_K: - return 'l';//L"л"; - case KC_L: - return 'd';//L"д"; - case KC_SEMICOLON: - return 'j';//L"ж"; - case KC_QUOTE: - return 'E';//L"э"; - case KC_Z: - return 'R';//L"я"; - case KC_X: - return 'q';//L"ч"; - case KC_C: - return 'c';//L"с"; - case KC_V: - return 'm';//L"м"; - case KC_B: - return 'n';//L"и"; - case KC_N: - return 't';//L"т"; - case KC_M: - return '=';//L"ь"; - case KC_COMMA: - return 'b';//L"б"; - case KC_DOT: - return 'u';//L"ю"; - default: - spdlog::warn("Unknown keycode passed to ru_kc_to_char"); - return '?';//L"?"; - } -} - - -std::string special_chars[] = { - - // Usual characters - "\\`", - "~", - "'", - "[", - "]", - "{", - "}", - "«", - "»", - - // Special characters - "Ѧ", - "Ѫ", - "Ԙ", - "¯\\_(ツ)_/¯" -}; - -// Ѧ ѧ little yus -// Ѫ ѫ big yus -// Ѩ ѩ iotified -// Ѭ ѭ iotofied -// Ԙ yae +// buffer size for waveform: +// (44100 / fps * 10), make 10 bigger for slower scrolling // -// 🙃 upside-down -// 😁 big grin -// 🙄 rolling eyes +// Double both buffer sizes if stereo + +// HID interface wrapper +Ergodox Dox = Ergodox::init( + HID_VENDOR_ID, + HID_PRODUCT_ID, + HID_USAGE, + HID_USAGE_PAGE +); + int main(int argc, char *argv[]) { - - Hunspell* hun = new Hunspell(HUNSPELL_AFF_EN, HUNSPELL_DIC_EN); - spdlog::set_level(spdlog::level::info); - // buffer size for waveform: - // (44100 / fps * 10), make 10 bigger for slower scrolling - // - // Double both buffer sizes if stereo - - // FFT generator - FFT_Visualizer fft = FFT_Visualizer( - width, height, MIN_HZ, MAX_HZ - ); - - // Audio buffer - Buffer buf = Buffer( - "/tmp/mpd.fifo", - 44100 / 2, // Keep 500ms of data in buffer - fft.compute_buffer_output_size() - ); - - // HID interface wrapper - Ergodox Dox = Ergodox::init( - HID_VENDOR_ID, - HID_PRODUCT_ID, - HID_USAGE, - HID_USAGE_PAGE - ); - - // Write buffer - uint8_t hid_buf[Dox.packet_size]; uint8_t last_layer_layout; Dox.connect_loop(); - - // Frame rate limiter - std::chrono::time_point< - std::chrono::steady_clock, - std::chrono::nanoseconds - > t = std::chrono::steady_clock::now(); - - std::chrono::time_point< - std::chrono::steady_clock, - std::chrono::nanoseconds - > last_fifo_sync = std::chrono::steady_clock::now(); - - struct mpd_connection *conn = mpd_connection_new(NULL, 0, 0); + #ifndef DISABLE_VISUALIZER + Visualizer::init(); + #endif while (1) { - memset(hid_buf, 0, sizeof(uint8_t) * Dox.packet_size); if (Dox.is_connected()) { - if (std::chrono::steady_clock::now() > t + std::chrono::milliseconds(30)) { - if (Dox.get_animation_mode() == 0x02) { - - // Animation data type - hid_buf[1] = CMD_ANIM_DATA_fft; - - if (std::chrono::steady_clock::now() > last_fifo_sync + std::chrono::seconds(10)) { - mpd_run_disable_output(conn, 1); - mpd_run_enable_output(conn, 1); - last_fifo_sync = std::chrono::steady_clock::now(); - spdlog::info("Synchronized fifo"); - } - - buf.update(); - fft.update(buf); - - for (size_t i = 0; i < 10; i++) { - // Get height from fft, apply bottom_skip - ssize_t h = fft.get_output()[i] - BOTTOM_SKIP; - - // Enforce max and min - // max implicitly enforces top_skip - h = h>KB_RESOLUTION ? KB_RESOLUTION : h; - h = h<0 ? 0 : h; - - hid_buf[i + 1] = h; - } - Dox.write(CMD_ANIM_DATA, hid_buf, Dox.packet_size); - } - - - t = std::chrono::steady_clock::now(); - } + #ifndef DISABLE_VISUALIZER + Visualizer::fn(Dox); + #endif // Dox.write might detect that we've been disconnected, // and Dox.read will fail if we are. @@ -448,53 +103,20 @@ int main(int argc, char *argv[]) { uint8_t cmd = Dox.read_buf[0]; switch (cmd) { + + #ifndef DISABLE_SPELL case CMD_SPELLCHECK_WORD: { - char word_chars[Dox.read_buf[1] + 1]; - - if (Dox.get_layer_layout() == LAYOUT_EN) { - for (int i=0; i < Dox.read_buf[1]; i++) { - // A in ascii: - // a in ascii: 0x61 - // KC_A: 0x04 - word_chars[i] = Dox.read_buf[i + 2] + 0x5D; - } - } else if (Dox.get_layer_layout() == LAYOUT_RU) { - for (int i=0; i < Dox.read_buf[1]; i++) { - word_chars[i] = ru_kc_to_char(Dox.read_buf[i + 2]); - } - } - word_chars[Dox.read_buf[1]] = 0x00; // Terminate with null char - std::string word = std::string(word_chars); - - int dp = hun->spell(word); - if (!dp) { - hid_buf[0] = 0x01; - Dox.write(CMD_SPELLCHECK_WORD, hid_buf, Dox.packet_size); - //spdlog::info("Got typo: \"{0:s}\" not in dict", word); - } - + Spell::do_cmd(Dox); break; } + #endif + #ifndef DISABLE_SPECIAL_CHAR case CMD_SPECIAL_CHAR: { - // Bytes 1,2: char id - uint16_t char_id = - (Dox.read_buf[2] << 8) | - (Dox.read_buf[1] << 0); - - spdlog::info("{0:d}", char_id); - - if (char_id < (sizeof(special_chars) / sizeof(std::string)) ) { - std::system( - ( - std::string("xdotool type \"") + - special_chars[char_id] + - std::string("\"") - ).c_str() - ); - } + SpecialChars::do_cmd(Dox); break; } + #endif } } @@ -528,6 +150,8 @@ int main(int argc, char *argv[]) { ); } - mpd_connection_free(conn); + #ifndef DISABLE_VISUALIZER + Visualizer::cleanup(); + #endif return 0; } \ No newline at end of file diff --git a/src/modules/special_chars.cpp b/src/modules/special_chars.cpp new file mode 100644 index 0000000..79221d8 --- /dev/null +++ b/src/modules/special_chars.cpp @@ -0,0 +1,25 @@ +#include "special_chars.hpp" +#ifndef DISABLE_SPECIAL_CHAR + + +namespace SpecialChars { + void do_cmd(Ergodox &Dox) { + // Bytes 1,2: char id + uint16_t char_id = + (Dox.read_buf[2] << 8) | + (Dox.read_buf[1] << 0); + + spdlog::info("{0:d}", char_id); + + if (char_id < (sizeof(special_chars) / sizeof(std::string))) { + std::system( + ( + std::string("xdotool type \"") + + special_chars[char_id] + + std::string("\"") + ).c_str() + ); + } + } +} +#endif \ No newline at end of file diff --git a/src/modules/special_chars.hpp b/src/modules/special_chars.hpp new file mode 100644 index 0000000..617ddef --- /dev/null +++ b/src/modules/special_chars.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "config.h" +#ifndef DISABLE_SPECIAL_CHAR + +#include +#include "ergodox.hpp" + +namespace SpecialChars { + const std::string special_chars[] = { + "\\`", + "~", + "'", + "[", + "]", + "{", + "}", + "«", + "»" + }; + + void do_cmd(Ergodox &Dox); +} + +#endif \ No newline at end of file diff --git a/src/modules/spell.cpp b/src/modules/spell.cpp new file mode 100644 index 0000000..db01b39 --- /dev/null +++ b/src/modules/spell.cpp @@ -0,0 +1,133 @@ +#include "spell.hpp" +#ifndef DISABLE_SPELL + +namespace Spell { + Hunspell* hun = new Hunspell(HUNSPELL_AFF_EN, HUNSPELL_DIC_EN); + uint8_t hid_buf[RAW_EPSIZE]; // Write buffer + + + // Replacement chars for now, since wide strings are hard. + char ru_kc_to_char(uint8_t keycode) { + switch (keycode) { + case KC_GRAVE: + return '~';//L"ё"; + case KC_1: + return '1';//L"1"; + case KC_2: + return '2';//L"2"; + case KC_3: + return '3';//L"3"; + case KC_4: + return '4';//L"4"; + case KC_5: + return '5';//L"5"; + case KC_6: + return '6';//L"6"; + case KC_7: + return '7';//L"7"; + case KC_8: + return '8';//L"8"; + case KC_9: + return '9';//L"9"; + case KC_0: + return '0';//L"0"; + case KC_Q: + return '^';//L"й"; + case KC_W: + return '*';//L"ц"; + case KC_E: + return 'y';//L"у"; + case KC_R: + return 'k';//L"к"; + case KC_T: + return 'e';//L"е"; + case KC_Y: + return 'H';//L"н"; + case KC_U: + return 'g';//L"г"; + case KC_I: + return 'w';//L"ш"; + case KC_O: + return 'W';//L"щ"; + case KC_P: + return 'z';//L"з"; + case KC_LEFT_BRACKET: + return 'x';//L"х"; + case KC_RIGHT_BRACKET: + return '!';//L"ъ"; + case KC_A: + return 'f';//L"ф"; + case KC_S: + return 'i';//L"ы"; + case KC_D: + return 'B';//L"в"; + case KC_F: + return 'a';//L"а"; + case KC_G: + return 'n';//L"п"; + case KC_H: + return 'p';//L"р"; + case KC_J: + return 'o';//L"о"; + case KC_K: + return 'l';//L"л"; + case KC_L: + return 'd';//L"д"; + case KC_SEMICOLON: + return 'j';//L"ж"; + case KC_QUOTE: + return 'E';//L"э"; + case KC_Z: + return 'R';//L"я"; + case KC_X: + return 'q';//L"ч"; + case KC_C: + return 'c';//L"с"; + case KC_V: + return 'm';//L"м"; + case KC_B: + return 'n';//L"и"; + case KC_N: + return 't';//L"т"; + case KC_M: + return '=';//L"ь"; + case KC_COMMA: + return 'b';//L"б"; + case KC_DOT: + return 'u';//L"ю"; + default: + spdlog::warn("Unknown keycode passed to ru_kc_to_char"); + return '?';//L"?"; + } + } + + + void do_cmd(Ergodox &Dox) { + char word_chars[Dox.read_buf[1] + 1]; + + if (Dox.get_layer_layout() == LAYOUT_EN) { + for (int i=0; i < Dox.read_buf[1]; i++) { + // A in ascii: + // a in ascii: 0x61 + // KC_A: 0x04 + word_chars[i] = Dox.read_buf[i + 2] + 0x5D; + } + } else if (Dox.get_layer_layout() == LAYOUT_RU) { + for (int i=0; i < Dox.read_buf[1]; i++) { + word_chars[i] = ru_kc_to_char(Dox.read_buf[i + 2]); + } + } + word_chars[Dox.read_buf[1]] = 0x00; // Terminate with null char + std::string word = std::string(word_chars); + + int dp = hun->spell(word); + if (!dp) { + memset(hid_buf, 0, sizeof(uint8_t) * Dox.packet_size); + hid_buf[0] = 0x01; + Dox.write(CMD_SPELLCHECK_WORD, hid_buf, Dox.packet_size); + //spdlog::info("Got typo: \"{0:s}\" not in dict", word); + } + } +} + +#endif \ No newline at end of file diff --git a/src/modules/spell.hpp b/src/modules/spell.hpp new file mode 100644 index 0000000..1454e5b --- /dev/null +++ b/src/modules/spell.hpp @@ -0,0 +1,181 @@ +#pragma once +#include "config.h" +#ifndef DISABLE_SPELL + +#include "hunspell.hxx" +#include "ergodox.hpp" + +namespace Spell { + char ru_kc_to_char(uint8_t keycode); + void do_cmd(Ergodox &Dox); +} + +enum hid_keyboard_keypad_usage { + KC_NO = 0x00, + KC_ROLL_OVER, + KC_POST_FAIL, + KC_UNDEFINED, + KC_A, + KC_B, + KC_C, + KC_D, + KC_E, + KC_F, + KC_G, + KC_H, + KC_I, + KC_J, + KC_K, + KC_L, + KC_M, // 0x10 + KC_N, + KC_O, + KC_P, + KC_Q, + KC_R, + KC_S, + KC_T, + KC_U, + KC_V, + KC_W, + KC_X, + KC_Y, + KC_Z, + KC_1, + KC_2, + KC_3, // 0x20 + KC_4, + KC_5, + KC_6, + KC_7, + KC_8, + KC_9, + KC_0, + KC_ENTER, + KC_ESCAPE, + KC_BACKSPACE, + KC_TAB, + KC_SPACE, + KC_MINUS, + KC_EQUAL, + KC_LEFT_BRACKET, + KC_RIGHT_BRACKET, // 0x30 + KC_BACKSLASH, + KC_NONUS_HASH, + KC_SEMICOLON, + KC_QUOTE, + KC_GRAVE, + KC_COMMA, + KC_DOT, + KC_SLASH, + KC_CAPS_LOCK, + KC_F1, + KC_F2, + KC_F3, + KC_F4, + KC_F5, + KC_F6, + KC_F7, // 0x40 + KC_F8, + KC_F9, + KC_F10, + KC_F11, + KC_F12, + KC_PRINT_SCREEN, + KC_SCROLL_LOCK, + KC_PAUSE, + KC_INSERT, + KC_HOME, + KC_PAGE_UP, + KC_DELETE, + KC_END, + KC_PAGE_DOWN, + KC_RIGHT, + KC_LEFT, // 0x50 + KC_DOWN, + KC_UP, + KC_NUM_LOCK, + KC_KP_SLASH, + KC_KP_ASTERISK, + KC_KP_MINUS, + KC_KP_PLUS, + KC_KP_ENTER, + KC_KP_1, + KC_KP_2, + KC_KP_3, + KC_KP_4, + KC_KP_5, + KC_KP_6, + KC_KP_7, + KC_KP_8, // 0x60 + KC_KP_9, + KC_KP_0, + KC_KP_DOT, + KC_NONUS_BACKSLASH, + KC_APPLICATION, + KC_KB_POWER, + KC_KP_EQUAL, + KC_F13, + KC_F14, + KC_F15, + KC_F16, + KC_F17, + KC_F18, + KC_F19, + KC_F20, + KC_F21, // 0x70 + KC_F22, + KC_F23, + KC_F24, + KC_EXECUTE, + KC_HELP, + KC_MENU, + KC_SELECT, + KC_STOP, + KC_AGAIN, + KC_UNDO, + KC_CUT, + KC_COPY, + KC_PASTE, + KC_FIND, + KC_KB_MUTE, + KC_KB_VOLUME_UP, // 0x80 + KC_KB_VOLUME_DOWN, + KC_LOCKING_CAPS_LOCK, + KC_LOCKING_NUM_LOCK, + KC_LOCKING_SCROLL_LOCK, + KC_KP_COMMA, + KC_KP_EQUAL_AS400, + KC_INTERNATIONAL_1, + KC_INTERNATIONAL_2, + KC_INTERNATIONAL_3, + KC_INTERNATIONAL_4, + KC_INTERNATIONAL_5, + KC_INTERNATIONAL_6, + KC_INTERNATIONAL_7, + KC_INTERNATIONAL_8, + KC_INTERNATIONAL_9, + KC_LANGUAGE_1, // 0x90 + KC_LANGUAGE_2, + KC_LANGUAGE_3, + KC_LANGUAGE_4, + KC_LANGUAGE_5, + KC_LANGUAGE_6, + KC_LANGUAGE_7, + KC_LANGUAGE_8, + KC_LANGUAGE_9, + KC_ALTERNATE_ERASE, + KC_SYSTEM_REQUEST, + KC_CANCEL, + KC_CLEAR, + KC_PRIOR, + KC_RETURN, + KC_SEPARATOR, + KC_OUT, // 0xA0 + KC_OPER, + KC_CLEAR_AGAIN, + KC_CRSEL, + KC_EXSEL, +}; + +#endif \ No newline at end of file diff --git a/src/modules/visualizer.cpp b/src/modules/visualizer.cpp new file mode 100644 index 0000000..5907320 --- /dev/null +++ b/src/modules/visualizer.cpp @@ -0,0 +1,76 @@ +#include "visualizer.hpp" +#ifndef DISABLE_VISUALIZER + +namespace Visualizer { + uint8_t hid_buf[RAW_EPSIZE]; // Write buffer + struct mpd_connection *conn; + + FFT_Visualizer fft = FFT_Visualizer( + width, height, MIN_HZ, MAX_HZ + ); + + std::chrono::time_point< + std::chrono::steady_clock, + std::chrono::nanoseconds + > t; + + std::chrono::time_point< + std::chrono::steady_clock, + std::chrono::nanoseconds + > last_fifo_sync; + + Buffer buf = Buffer( + "/tmp/mpd.fifo", + 44100 / 2, // Keep 500ms of data in buffer + fft.compute_buffer_output_size() + ); + + void init() { + // Frame rate limiter + t = std::chrono::steady_clock::now(); + last_fifo_sync = std::chrono::steady_clock::now(); + conn = mpd_connection_new(NULL, 0, 0); + } + + void cleanup() { + mpd_connection_free(Visualizer::conn); + } + + void fn(Ergodox &Dox) { + if (std::chrono::steady_clock::now() > t + std::chrono::milliseconds(30)) { + if (Dox.get_animation_mode() == 0x02) { + + // Animation data type + hid_buf[1] = CMD_ANIM_DATA_fft; + + if (std::chrono::steady_clock::now() > last_fifo_sync + std::chrono::seconds(10)) { + mpd_run_disable_output(conn, 1); + mpd_run_enable_output(conn, 1); + last_fifo_sync = std::chrono::steady_clock::now(); + spdlog::info("Synchronized fifo"); + } + + buf.update(); + fft.update(buf); + + for (size_t i = 0; i < 10; i++) { + // Get height from fft, apply bottom_skip + ssize_t h = fft.get_output()[i] - BOTTOM_SKIP; + + // Enforce max and min + // max implicitly enforces top_skip + h = h>KB_RESOLUTION ? KB_RESOLUTION : h; + h = h<0 ? 0 : h; + + hid_buf[i + 1] = h; + } + Dox.write(CMD_ANIM_DATA, hid_buf, Dox.packet_size); + } + + + t = std::chrono::steady_clock::now(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/modules/visualizer.hpp b/src/modules/visualizer.hpp new file mode 100644 index 0000000..b731f44 --- /dev/null +++ b/src/modules/visualizer.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "config.h" +#ifndef DISABLE_VISUALIZER + +#include "utility/buffer.hpp" +#include +#include "mpd/client.h" +#include "signal_processing/fft.hpp" +#include "spdlog/spdlog.h" +#include "ergodox.hpp" +#include "commands.h" + +namespace Visualizer { + + const size_t width = 10; + const size_t height = BOTTOM_SKIP + KB_RESOLUTION + TOP_SKIP; + + void cleanup(); + void init(); + void fn(Ergodox &Dox); +} + +#endif \ No newline at end of file