#include "MainLoop.h" #include "Timer.h" #include "config.h" #include "log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; using namespace std::string_literals; namespace bp = boost::process; MainLoop::MainLoop(int argc, char** argv): m_status{}, m_config{argc, argv}, m_note_click{m_config}, m_clock_click{}, m_internal_click{m_config}, m_midi{}, m_pcm{m_config}, m_ui{m_config} { m_status.add(LED{"/sys/class/leds/ACT", "/sys/class/leds/PWR"}); m_status.add(LED{"/sys/class/leds/thingm1:green:led1", "/sys/class/leds/thingm1:red:led1"}); start_fcgi(); } MainLoop::~MainLoop() { stop_fcgi(); } void MainLoop::start_fcgi() { m_child_fcgi = bp::child("spawn-fcgi -a 127.0.0.1 -p 9090 -n -- ./click-fcgi"); if (!m_child_fcgi.valid() || !m_child_fcgi.running()) { throw std::runtime_error("click-fcgi not started"); } } void MainLoop::stop_fcgi() { if (m_child_fcgi.valid()) { m_child_fcgi.terminate(); } } bool run_flag = true; void signal_handler(int) { run_flag = false; std::cout << "Signal received. Terminating." << std::endl; } void MainLoop::reconfigure_mode() { debug_cout << "reconfiguring mode" << std::endl; m_click_connection.disconnect(); int mode = m_config.get_mode(); if (mode == 0) { m_click_connection = m_note_click.signal_click.connect([&](){m_pcm.click({});}); } else if (mode == 1) { m_click_connection = m_clock_click.signal_click.connect([&](){m_pcm.click({});}); } else if (mode == 2) { m_click_connection = m_internal_click.signal_click.connect([&](std::chrono::duration offset){m_pcm.click(offset);}); } else { log_cout << fmt::format("Error: Unknown mode: {}", mode) << std::endl; } } int MainLoop::run() { try { signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); //debug_cout.activate(); log_cout.activate(); log_cout.log_lines(log_lines); m_pcm.write(); Timer timer_50ms(50ms, true); timer_50ms.start(); Timer timer_500ms(500ms, true); timer_500ms.start(); Timer timer_10min(10min, true); timer_10min.start(); // Main signals boost::signals2::signal signal_count_loops; // // Signal-Slot Connections: // m_midi.signal_note.connect([&](int channel, int note, uint64_t timestamp){m_note_click.receive_note(channel, note, timestamp);}); //m_click_connection = m_note_click.signal_click.connect([&](){m_pcm.click(0);}); reconfigure_mode(); m_note_click.signal_bpm.connect([&](int bpm){m_ui.slot_note_bpm(bpm);}); m_clock_click.signal_bpm.connect([&](int bpm){m_ui.slot_clock_bpm(bpm);}); m_midi.signal_active_sensing.connect([&](){m_ui.slot_active_sensing();}); timer_500ms.elapsed.connect([&](){m_ui.draw();}); signal_count_loops.connect([&](){m_ui.count_main_loops();}); m_midi.signal_count_events.connect([&](size_t size){m_ui.count_midi_events(size);}); timer_10min.elapsed.connect([&](){m_config.persist();}); m_midi.signal_note.connect([&](int channel, int note, uint64_t timestamp){m_ui.slot_midi_note(channel, note, timestamp);}); m_midi.signal_clock.connect([&](){m_clock_click.receive_clock();}); m_config.signal_mode.connect([&](int mode){reconfigure_mode();}); timer_50ms.elapsed.connect([&](){m_internal_click.run_cyclic_50ms();}); m_midi.flush(); while (run_flag) { debug_cout << "Main loop entered." << std::endl; signal_count_loops(); fd_set read_set; FD_ZERO(&read_set); FD_SET(m_midi.fd(), &read_set); FD_SET(0, &read_set); if constexpr (0) { // PCM fd almost always writeable: for single frames at high speed fd_set write_set; FD_ZERO(&write_set); FD_SET(m_pcm.fd(), &write_set); } struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 20000; int result = select(FD_SETSIZE, &read_set, nullptr/*&write_set*/, nullptr, &timeout); if (result < 0) { throw std::runtime_error("select() failed"); } else if (result == 0) { debug_cout << "select() timeout" << std::endl; } while (m_midi.event_ready()) { //std::cout << "read..." << std::endl; auto event = m_midi.read(); //std::cout << "process..." << std::endl; m_midi.process(event); } if (m_pcm.write_available()) { //std::cout << "DEBUG: WRITE" << std::endl; m_pcm.write(); } while (m_ui.key_available()) { m_ui.handle_input(); } // handle timers, TODO: make updates more efficient at scale timer_50ms.update(); timer_500ms.update(); timer_10min.update(); } return 0; } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; m_status.setMode(StatusLED::Mode::Error); return 1; } }