#include "UI.h" #include "cpuload.h" #include "debug.h" #include "log.h" #include #include #include #include #include #include #include const int midi_monitor_max_size = 3; using namespace std::chrono_literals; namespace { const int midi_bandwidth = 3125; // bytes/s std::vector mode_names{ "NoteClick", "Clock", "Internal" }; std::vector output_names{ "Off", "On" }; std::vector active_sensing_names{ "Not Detected", "Detected" }; } IntervalCounter::IntervalCounter() { } int IntervalCounter::get_count_per_second() { // calculate result std::chrono::time_point now = clock_type::now(); uint64_t diff_ms = std::chrono::duration_cast(now - m_checkpoint_timestamp).count(); uint64_t count_per_second = (diff_ms == 0 || m_count_checkpoint == 0) ? 0 : ((m_count - m_count_checkpoint) * 1000 / diff_ms); // update state m_checkpoint_timestamp = now; m_count_checkpoint = m_count; return count_per_second; } void IntervalCounter::count(uint64_t n) { m_count += n; } UI::UI(Config& config): m_config(config), m_main_loops{}, m_midi_events{}, m_midi_bytes{}, m_midi_notes{}, m_active_sensing_timestamp{}, m_midi_timestamp{}, m_midi_monitor{30s}, m_note_bpm{2000ms}, m_clock_bpm{2000ms}, m_touchpad{} { } bool stdin_available() { struct pollfd fds{}; fds.fd = 0; // stdin fds.events = POLLIN; int ret = poll(&fds, 1, 0); if (ret == 0) return false; else if (ret == 1) return true; else log_cout << "UI: Poll error" << std::endl; return false; } bool UI::key_available() { return stdin_available() || m_touchpad.event_available(); } void UI::draw() { std::vector cpuloads = get_cpu_loads(); int main_loops_per_second = m_main_loops.get_count_per_second(); int midi_events_per_second = m_midi_events.get_count_per_second(); int midi_bytes_per_second = m_midi_bytes.get_count_per_second(); int midi_notes_per_second = m_midi_notes.get_count_per_second(); int temperature = m_temperature.read_degree(); int mode = m_config.get_mode(); int channel = m_config.get_midi_channel(); int note = m_config.get_midi_note(); int internal_bpm = m_config.get_bpm(); int bpm; if (mode == 0) { bpm = m_note_bpm.value(); } else if (mode == 1) { bpm = m_clock_bpm.value(); } else if (mode == 2) { bpm = m_config.get_bpm(); } int output = m_config.get_output(); int active_sensing_detected = (clock_type::now() - m_active_sensing_timestamp) < 2s ? 1 : 0; fmt::text_style editable_color{fg(fmt::color::crimson) | fmt::emphasis::bold}; fmt::text_style value_color{fg(fmt::color::blue) | fmt::emphasis::bold}; fmt::text_style input_color{fg(fmt::color::green) | fmt::emphasis::bold}; // clear screen std::cout << "\x1B[2J\x1B[H"; //std::cout << std::endl; std::cout << fmt::format(editable_color, "Speed: {:3}", bpm) << " BPM " << fmt::format(input_color, "(+/-)") << std::endl; std::cout << "Mode " << fmt::format(input_color, "(m)") << ": "; for (int i = 0; i < mode_names.size(); ++i) { if (i == mode) { std::cout << fmt::format(editable_color, " {}", mode_names[i]); } else { std::cout << fmt::format(" {}", mode_names[i]); } } std::cout << std::endl; std::cout << "MIDI Note " << fmt::format(input_color, "(n)") << ": " << fmt::format(editable_color, "{}/{}", channel, note); std::cout << std::endl; std::cout << "Audio Output " << fmt::format(input_color, "(o)") << ": " << fmt::format(editable_color, "{}", output_names[output]); std::cout << std::endl; std::cout << std::endl; std::cout << "Status:" << std::endl; std::cout << " MIDI Notes: "; if (m_midi_monitor.value().empty()) { std::cout << "--"; } else { int count{}; auto midi_monitor = m_midi_monitor.value(); for (const auto& i: midi_monitor) { if (count == 0) { std::cout << fmt::format(editable_color, " {}/{}", i.first, i.second); } else { std::cout << fmt::format(" {}/{}", i.first, i.second); } ++count; } if (count >= midi_monitor_max_size) { std::cout << " ..."; } } std::cout << std::endl; std::cout << fmt::format(" MIDI Timestamp: {}", m_midi_timestamp) << std::endl; std::cout << " MIDI Active Sensing: " << fmt::format(value_color, "{}", active_sensing_names[active_sensing_detected]) << std::endl; std::cout << fmt::format(" MIDI Events/s:{:3}", midi_events_per_second) << std::endl; std::cout << fmt::format(" MIDI Notes/s: {:3}", midi_notes_per_second) << std::endl; std::cout << fmt::format(" MIDI Bytes/s: {:3} ({}%)", midi_bytes_per_second, midi_bytes_per_second * 100 / midi_bandwidth) << std::endl; std::cout << fmt::format(" MIDI Click: {:3} BPM", m_note_bpm.value()) << std::endl; std::cout << fmt::format(" MIDI Clock: {:3} BPM", m_clock_bpm.value()) << std::endl; std::cout << fmt::format(" Internal: {:3} BPM", internal_bpm) << std::endl; std::cout << " CPU:"; for (auto& i: cpuloads) { std::cout << fmt::format(" {:3}%", i); } int max = *std::max_element(cpuloads.begin(), cpuloads.end()); std::cout << fmt::format(", max. {:3}%", max); std::cout << fmt::format(" Temperature: {:3} C", temperature) << std::endl; std::cout << fmt::format(" Main loops/s: {:3}", main_loops_per_second) << std::endl; std::cout << std::endl; std::cout << "Log:" << std::endl; std::cout << log_cout.get_log(); } void UI::handle_input() { // handle console key if (stdin_available()) { char c; std::cin >> c; debug_cout << "Key: " << std::to_string(c) << std::endl; if (c == '+') { if (m_config.get_bpm() < 240) { m_config.set_bpm(m_config.get_bpm() + 1); } } else if (c == '-') { if (m_config.get_bpm() > 10) { m_config.set_bpm(m_config.get_bpm() - 1); } } else if (c == 'm') { m_config.set_mode((m_config.get_mode() + 1) % 3); } else if (c == 'n') { auto midi_monitor = m_midi_monitor.value(); if (!midi_monitor.empty()) { m_config.set_midi_channel(midi_monitor.front().first); m_config.set_midi_note(midi_monitor.front().second); } } else if (c == 'o') { m_config.set_output(1 - m_config.get_output()); } else { log_cout << fmt::format("Unknown key: {}", c) << std::endl; } } // handle touchpad if (m_touchpad.event_available()) { input_event ev = m_touchpad.get_event(); if (m_touchpad.event_is_button1(ev)) { m_config.set_mode((m_config.get_mode() + 1) % 3); } else if (m_touchpad.event_is_button2(ev)) { auto midi_monitor = m_midi_monitor.value(); if (!midi_monitor.empty()) { m_config.set_midi_channel(midi_monitor.front().first); m_config.set_midi_note(midi_monitor.front().second); } } } } void UI::count_main_loops() { m_main_loops.count(); } void UI::count_midi_events(size_t size) { m_midi_events.count(); m_midi_bytes.count(size); } void UI::count_midi_notes() { m_midi_notes.count(); } void UI::slot_active_sensing() { m_active_sensing_timestamp = clock_type::now(); } void UI::slot_midi_note(int channel, int note, uint64_t timestamp) { m_midi_timestamp = timestamp; auto& midi_monitor = m_midi_monitor.update(); midi_monitor.emplace_front(channel, note); while (midi_monitor.size() > midi_monitor_max_size) { midi_monitor.pop_back(); } count_midi_notes(); } void UI::slot_note_bpm(int bpm) { m_note_bpm.update(bpm); } void UI::slot_clock_bpm(int bpm) { m_clock_bpm.update(bpm); }