diff options
| -rw-r--r-- | Click.cpp | 2 | ||||
| -rw-r--r-- | Click.h | 75 | ||||
| -rw-r--r-- | ClockClick.cpp | 22 | ||||
| -rw-r--r-- | ClockClick.h | 27 | ||||
| -rw-r--r-- | InternalClick.cpp | 19 | ||||
| -rw-r--r-- | InternalClick.h | 28 | ||||
| -rw-r--r-- | MainLoop.cpp | 160 | ||||
| -rw-r--r-- | MainLoop.h | 33 | ||||
| -rw-r--r-- | Makefile | 7 | ||||
| -rw-r--r-- | NoteClick.cpp | 20 | ||||
| -rw-r--r-- | NoteClick.h | 27 | ||||
| -rw-r--r-- | PCM.cpp | 4 | ||||
| -rw-r--r-- | PCM.h | 2 | ||||
| -rw-r--r-- | UI.cpp | 87 | ||||
| -rw-r--r-- | UI.h | 13 | ||||
| -rw-r--r-- | main.cpp | 140 | 
16 files changed, 435 insertions, 231 deletions
| diff --git a/Click.cpp b/Click.cpp deleted file mode 100644 index 4083e0d..0000000 --- a/Click.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "Click.h" - diff --git a/Click.h b/Click.h deleted file mode 100644 index 6d3d871..0000000 --- a/Click.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include <boost/signals2.hpp> - -#include "config.h" -#include "BPMDetect.h" - -// Internally generated -// Configured via: BPM -class InternalClick -{ -public: -  InternalClick(Config& config): m_config(config){} - -private: -  Config& m_config; -}; - -// Generated from MIDI notes -// Configured via channel and note to listen to -class NoteClick -{ -public: -  // signals -  boost::signals2::signal<void()> signal_click; -  boost::signals2::signal<void(int)> signal_bpm; - -  NoteClick(Config& config): -    m_channel(config.get_midi_channel()), -    m_note(config.get_midi_note()), -    m_detect(1) -  { -    m_detect.signal_bpm.connect([&](int bpm){signal_bpm(bpm);}); -  } - -  // slots -  void receive_note(int channel, int note, uint64_t timestamp) -  { -    (void) timestamp; - -    if (true || (channel == m_channel && note == m_note)) { -      signal_click(); -      m_detect.receive_event(); -    } -  } - -private: -  int m_channel; -  int m_note; - -  BPMDetect m_detect; -}; - -// Generated from MIDI Clock -class ClockClick -{ -public: -  ClockClick(): m_detect(24) -  { -    m_detect.signal_bpm.connect([&](int bpm){signal_bpm(bpm);}); -  } - -  // signals -  boost::signals2::signal<void(int)> signal_bpm; - -  // slots -  void receive_clock() -  { -    m_detect.receive_event(); -  } - -private: -  BPMDetect m_detect; -}; - diff --git a/ClockClick.cpp b/ClockClick.cpp new file mode 100644 index 0000000..c9f6938 --- /dev/null +++ b/ClockClick.cpp @@ -0,0 +1,22 @@ +#include "ClockClick.h" + +ClockClick::ClockClick(): m_divider(24), m_count{}, m_detect(24) +{ +  m_detect.signal_bpm.connect([&](int bpm){signal_bpm(bpm);}); +} + +// slots +void ClockClick::receive_clock() +{ +  m_detect.receive_event(); + +  // guard by divider +  ++m_count; +  if (m_count < m_divider) { +    return; +  } else { +    m_count = 0; +  } + +  signal_click(); +} diff --git a/ClockClick.h b/ClockClick.h new file mode 100644 index 0000000..2c0ef19 --- /dev/null +++ b/ClockClick.h @@ -0,0 +1,27 @@ +#pragma once + +#include <boost/signals2.hpp> + +#include "config.h" +#include "BPMDetect.h" + +// Generated from MIDI Clock +class ClockClick +{ +public: +  ClockClick(); + +  // signals +  boost::signals2::signal<void()> signal_click; +  boost::signals2::signal<void(int)> signal_bpm; + +  // slots +  void receive_clock(); + +private: +  const int m_divider; +  int m_count; + +  BPMDetect m_detect; +}; + diff --git a/InternalClick.cpp b/InternalClick.cpp new file mode 100644 index 0000000..33654fd --- /dev/null +++ b/InternalClick.cpp @@ -0,0 +1,19 @@ +#include "InternalClick.h" + +InternalClick::InternalClick(Config& config): +  m_config{config}, +  m_timestamp{clock_type::now()} +{ +} + +void InternalClick::run_cyclic_50ms() +{ +  std::chrono::time_point<clock_type> now = clock_type::now(); + +  std::chrono::duration<double> duration{60.0 / static_cast<double>(m_config.get_bpm())}; +  if (now - m_timestamp > duration) { +    m_timestamp = now; +    signal_click(0); // offset +  } +} + diff --git a/InternalClick.h b/InternalClick.h new file mode 100644 index 0000000..1fffb6c --- /dev/null +++ b/InternalClick.h @@ -0,0 +1,28 @@ +#pragma once + +#include <boost/signals2.hpp> + +#include "config.h" +#include "BPMDetect.h" + +using clock_type = std::chrono::high_resolution_clock; + +// Internally generated +// Configured via: BPM +class InternalClick +{ +public: +  InternalClick(Config& config); + +  // signals +  boost::signals2::signal<void(int64_t)> signal_click; + +  // slots +  void run_cyclic_50ms(); + +private: +  Config& m_config; + +  std::chrono::time_point<clock_type> m_timestamp; +}; + diff --git a/MainLoop.cpp b/MainLoop.cpp new file mode 100644 index 0000000..1bd0e1d --- /dev/null +++ b/MainLoop.cpp @@ -0,0 +1,160 @@ +#include "MainLoop.h" + +#include "Timer.h" +#include "config.h" +#include "log.h" + +#include <chrono> +#include <cmath> +#include <cstdint> +#include <exception> +#include <iostream> +#include <limits> +#include <memory> +#include <stdexcept> + +#include <stdio.h> +#include <sys/types.h> +#include <sys/time.h> +#include <time.h> +#include <signal.h> + +using namespace std::chrono_literals; +using namespace std::string_literals; + +MainLoop::MainLoop(): +  m_config{}, +  m_note_click{m_config}, +  m_clock_click{}, +  m_internal_click{m_config}, +  m_midi{}, +  m_pcm{}, +  m_ui{m_config} +{ +} + +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(0);}); +  } else if (mode == 1) { +    m_click_connection = m_clock_click.signal_click.connect([&](){m_pcm.click(0);}); +  } else if (mode == 2) { +    m_click_connection = m_internal_click.signal_click.connect([&](int64_t offset){m_pcm.click(offset);}); +  } else { +    log_cout << fmt::format("Error: Unknown mode: {}", mode) << std::endl; +  } +} + +int MainLoop::run() +{ +  signal(SIGTERM, signal_handler); +  signal(SIGINT, signal_handler); + +  try { +    //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<void()> 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([&](){m_ui.count_midi_events();}); +    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(); +      } + +      if (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(); +    } +  } catch (const std::exception& ex) { +    std::cerr << "Error: " << ex.what() << std::endl; +  } + +  return 0; +} + diff --git a/MainLoop.h b/MainLoop.h new file mode 100644 index 0000000..0aac227 --- /dev/null +++ b/MainLoop.h @@ -0,0 +1,33 @@ +#pragma once + +#include "NoteClick.h" +#include "ClockClick.h" +#include "InternalClick.h" +#include "MIDI.h" +#include "PCM.h" +#include "UI.h" + +#include <boost/signals2.hpp> + +class MainLoop +{ +public: +  MainLoop(); +  int run(); + +private: +  void reconfigure_mode(); + +  boost::signals2::connection m_click_connection; + +  Config m_config; + +  NoteClick m_note_click; +  ClockClick m_clock_click; +  InternalClick m_internal_click; + +  MIDI m_midi; +  PCM m_pcm; +  UI m_ui; +}; + @@ -3,6 +3,7 @@ TARGET=click  default: $(TARGET)  SRCS= \ +  MainLoop.cpp \    main.cpp \    MIDI.cpp \    PCM.cpp \ @@ -12,8 +13,10 @@ SRCS= \    debug.cpp \    cpuload.cpp \    log.cpp \ -  Click.cpp \ -  BPMDetect.cpp +  NoteClick.cpp \ +  ClockClick.cpp \ +  InternalClick.cpp \ +  BPMDetect.cpp \  HEADERS=$(SRCS:.cpp=.h) diff --git a/NoteClick.cpp b/NoteClick.cpp new file mode 100644 index 0000000..657a893 --- /dev/null +++ b/NoteClick.cpp @@ -0,0 +1,20 @@ +#include "NoteClick.h" + +NoteClick::NoteClick(Config& config): +  m_config(config), +  m_detect(1) +{ +  m_detect.signal_bpm.connect([&](int bpm){signal_bpm(bpm);}); +} + +// slots +void NoteClick::receive_note(int channel, int note, uint64_t timestamp) +{ +  (void) timestamp; + +  if (channel == m_config.get_midi_channel() && note == m_config.get_midi_note()) { +    signal_click(); +    m_detect.receive_event(); +  } +} + diff --git a/NoteClick.h b/NoteClick.h new file mode 100644 index 0000000..754329f --- /dev/null +++ b/NoteClick.h @@ -0,0 +1,27 @@ +#pragma once + +#include <boost/signals2.hpp> + +#include "config.h" +#include "BPMDetect.h" + +// Generated from MIDI notes +// Configured via channel and note to listen to +class NoteClick +{ +public: +  NoteClick(Config& config); + +  // signals +  boost::signals2::signal<void()> signal_click; +  boost::signals2::signal<void(int)> signal_bpm; + +  // slots +  void receive_note(int channel, int note, uint64_t timestamp); + +private: +  Config& m_config; + +  BPMDetect m_detect; +}; + @@ -59,13 +59,13 @@ PCM::~PCM()    free(pfd);  } -void PCM::click() +void PCM::click(int64_t offset_us)  {    snd_pcm_sframes_t delay;    if (0 > snd_pcm_delay(handle, &delay)) {    } -  m_phase = - click_latency_frames + delay; +  m_phase = - click_latency_frames + delay + offset_us;  }  // generate 1 buffer size @@ -21,7 +21,7 @@ public:    PCM();    ~PCM(); -  void click(); +  void click(int64_t offset_us);    // generate 1 buffer size    void generate(); @@ -11,6 +11,9 @@  #include <fmt/color.h>  #include <fmt/format.h> +#include <stdio.h> +#include <sys/poll.h> +  const int midi_monitor_max_size = 3;  using namespace std::chrono_literals; @@ -62,6 +65,31 @@ UI::UI(Config& config):  {  } +bool UI::key_available() { +  struct pollfd fds{}; +  int ret; +  fds.fd = 0; // stdin +  fds.events = POLLIN; +  ret = poll(&fds, 1, 0); +  if (ret == 0) +    return false; +  else if (ret == 1) +    return true; +  else +    log_cout << "Error" << std::endl; +  return false; +} + +int read_key() { +  char buf[1]; +  int got = read(0, buf, 1); +  if (got != 1) { +    log_cout << "Bad input size: " << std::to_string(got) << std::endl; +  } + +  return buf[0]; +} +  void UI::draw()  {    std::vector<int> cpuloads = get_cpu_loads(); @@ -71,28 +99,40 @@ void UI::draw()    int mode = m_config.get_mode();    int channel = m_config.get_midi_channel();    int note = m_config.get_midi_note(); -  int bpm = m_config.get_bpm(); +  int internal_bpm = m_config.get_bpm(); +  int bpm; +  if (mode == 0) { +    bpm = m_note_bpm; +  } else if (mode == 1) { +    bpm = m_clock_bpm; +  } 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}; +    // clear screen    std::cout << "\x1B[2J\x1B[H";    //std::cout << std::endl; -  std::cout << fmt::format("- {:3} BPM +", bpm) << std::endl; +  std::cout << "- " << fmt::format(editable_color, "{:3}", bpm) << " BPM +" << std::endl;    std::cout << "Mode: ";    for (int i = 0; i < mode_names.size(); ++i) {      if (i == mode) { -      std::cout << fmt::format(fg(fmt::color::crimson) | fmt::emphasis::bold, " {}", mode_names[i]); +      std::cout << fmt::format(editable_color, " {}", mode_names[i]);      } else {        std::cout << fmt::format(" {}", mode_names[i]);      }    }    std::cout << std::endl; -  std::cout << fmt::format("MIDI Note: {}/{}", channel, note); +  std::cout << "MIDI Note: " << fmt::format(editable_color, "{}/{}", channel, note);    std::cout << std::endl; -  std::cout << "Audio Output: " << fmt::format(fg(fmt::color::crimson) | fmt::emphasis::bold, "{}", output_names[output]); +  std::cout << "Audio Output: " << fmt::format(editable_color, "{}", output_names[output]);    std::cout << std::endl;    std::cout << std::endl;    std::cout << "Status:" << std::endl; @@ -111,7 +151,7 @@ void UI::draw()      int count{};      for (const auto& i: m_midi_monitor) {        if (count == 0) { -        std::cout << fmt::format(fg(fmt::color::crimson) | fmt::emphasis::bold, " {}/{}", i.first, i.second); +        std::cout << fmt::format(editable_color, " {}/{}", i.first, i.second);        } else {          std::cout << fmt::format(" {}/{}", i.first, i.second);        } @@ -123,11 +163,11 @@ void UI::draw()    }    std::cout << std::endl;    std::cout << fmt::format("  MIDI Timestamp: {}", m_midi_timestamp) << std::endl; -  std::cout << "  MIDI Active Sensing: " << fmt::format(fg(fmt::color::blue) | fmt::emphasis::bold, "{}", active_sensing_names[active_sensing_detected]) << 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: {}", midi_events_per_second) << std::endl;    std::cout << fmt::format("  MIDI Clock: {:3} BPM", m_clock_bpm) << std::endl;    std::cout << fmt::format("  MIDI Click: {:3} BPM", m_note_bpm) << std::endl; -  std::cout << fmt::format("  Internal:   {:3} BPM", bpm) << std::endl; +  std::cout << fmt::format("  Internal:   {:3} BPM", internal_bpm) << std::endl;    std::cout << fmt::format("  Main loops/s: {}", main_loops_per_second) << std::endl; @@ -135,6 +175,37 @@ void UI::draw()    std::cout << log_cout.get_log() << std::endl;  } +void UI::handle_input() +{ +  if (key_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') { +      if (!m_midi_monitor.empty()) { +        m_config.set_midi_channel(m_midi_monitor.front().first); +        m_config.set_midi_note(m_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; +    } +  } +} +  void UI::count_main_loops()  {    m_main_loops.count(); @@ -31,12 +31,15 @@ public:    void draw(); +  void handle_input(); + +  bool key_available(); +    // signals, from user input -  boost::signals2::signal<void()> bpm_plus; -  boost::signals2::signal<void()> bpm_minus; -  boost::signals2::signal<void()> note_set_from_midi; -  boost::signals2::signal<void()> mode; -  boost::signals2::signal<void()> output; +  boost::signals2::signal<void(int)> signal_bpm; +  boost::signals2::signal<void(int, int)> signal_note_set_from_midi; +  boost::signals2::signal<void(int)> signal_mode; +  boost::signals2::signal<void(int)> signal_output;    // slots    void count_main_loops(); @@ -1,138 +1,6 @@ -#include "Click.h" -#include "MIDI.h" -#include "PCM.h" -#include "Timer.h" -#include "UI.h" -#include "config.h" -#include "log.h" +#include "MainLoop.h" -#include <chrono> -#include <cmath> -#include <cstdint> -#include <exception> -#include <iostream> -#include <limits> -#include <memory> -#include <stdexcept> - -#include <stdio.h> -#include <sys/types.h> -#include <sys/time.h> -#include <time.h> -#include <signal.h> - -#include <boost/signals2.hpp> - -using namespace std::chrono_literals; -using namespace std::string_literals; - -double diff_timespec(const struct timespec *time1, const struct timespec *time0) { -  return (time1->tv_sec - time0->tv_sec) -      + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; -} - -bool run_flag = true; - -void signal_handler(int) { -  run_flag = false; -  std::cout << "Signal received. Terminating." << std::endl; -} - -int main(void) -{ -  signal(SIGTERM, signal_handler); -  signal(SIGINT, signal_handler); - -  try { -    //debug_cout.activate(); -    log_cout.activate(); -    log_cout.log_lines(log_lines); - -    Config config; - -    ClockClick clock_click; -    NoteClick note_click(config); -    InternalClick internal_click(config); - -    MIDI midi; -    PCM pcm; -    UI ui(config); - -    pcm.write(); - -    Timer timer_500ms(500ms, true); -    timer_500ms.start(); - -    Timer timer_10min(10min, true); -    timer_10min.start(); - -    // Main signals -    boost::signals2::signal<void()> signal_count_loops; - -    // -    // Signal-Slot Connections: -    // -    midi.signal_note.connect([&](int channel, int note, uint64_t timestamp){note_click.receive_note(channel, note, timestamp);}); -    note_click.signal_click.connect([&](){pcm.click();}); -    note_click.signal_bpm.connect([&](int bpm){ui.slot_note_bpm(bpm);}); -    clock_click.signal_bpm.connect([&](int bpm){ui.slot_clock_bpm(bpm);}); -    midi.signal_active_sensing.connect([&](){ui.slot_active_sensing();}); -    timer_500ms.elapsed.connect([&](){ui.draw();}); -    signal_count_loops.connect([&](){ui.count_main_loops();}); -    midi.signal_count_events.connect([&](){ui.count_midi_events();}); -    timer_10min.elapsed.connect([&](){config.persist();}); -    midi.signal_note.connect([&](int channel, int note, uint64_t timestamp){ui.slot_midi_note(channel, note, timestamp);}); -    midi.signal_clock.connect([&](){clock_click.receive_clock();}); - -    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(midi.fd(), &read_set); - -#if 0 -      // PCM fd almost always writeable: for single frames at high speed -      fd_set write_set; -      FD_ZERO(&write_set); -      FD_SET(pcm.fd(), &write_set); -#endif - -      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 (midi.event_ready()) -      { -        //std::cout << "read..." << std::endl; -        auto event = midi.read(); -        //std::cout << "process..." << std::endl; -        midi.process(event); -      } - -      if (pcm.write_available()) { -        //std::cout << "DEBUG: WRITE" << std::endl; -        pcm.write(); -      } - -      // handle timers, TODO: make updates more efficient at scale -      timer_500ms.update(); -      timer_10min.update(); -    } -  } catch (const std::exception& ex) { -    std::cerr << "Error: " << ex.what() << std::endl; -  } -  -  return 0; +int main() { +  MainLoop main_loop; +  return main_loop.run();  } - | 
