summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--BPMDetect.cpp37
-rw-r--r--BPMDetect.h27
-rw-r--r--Click.h33
-rw-r--r--MIDI.cpp2
-rw-r--r--MIDI.h1
-rw-r--r--Makefile5
-rw-r--r--UI.cpp88
-rw-r--r--UI.h32
-rw-r--r--config.cpp22
-rw-r--r--config.h6
-rw-r--r--main.cpp7
11 files changed, 217 insertions, 43 deletions
diff --git a/BPMDetect.cpp b/BPMDetect.cpp
new file mode 100644
index 0000000..756747d
--- /dev/null
+++ b/BPMDetect.cpp
@@ -0,0 +1,37 @@
+#include "BPMDetect.h"
+
+#include "log.h"
+
+BPMDetect::BPMDetect(int divider):
+ m_divider{divider},
+ m_count{}
+{
+}
+
+// Strategy: evaluate last 3 valid beats
+// always skip m_divider-1 beats
+void BPMDetect::receive_event()
+{
+ // guard by divider
+ ++m_count;
+ if (m_count < m_divider) {
+ return;
+ } else {
+ m_count = 0;
+ }
+
+ // calculate bpm
+ time_point now = clock_type::now();
+
+ m_timestamps.push_back(now);
+
+ while (m_timestamps.size() > 3) {
+ m_timestamps.pop_front();
+ }
+
+ if (m_timestamps.size() == 3) {
+ int bpm = 60 * 2 / std::chrono::duration<double>(m_timestamps.back() - m_timestamps.front()).count();
+
+ signal_bpm(bpm);
+ }
+}
diff --git a/BPMDetect.h b/BPMDetect.h
new file mode 100644
index 0000000..7cd926e
--- /dev/null
+++ b/BPMDetect.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <boost/signals2.hpp>
+
+#include <chrono>
+#include <deque>
+
+class BPMDetect
+{
+public:
+ BPMDetect(int divider);
+ void receive_event();
+
+ // signal
+ boost::signals2::signal<void(int)> signal_bpm;
+
+private:
+ using clock_type = std::chrono::high_resolution_clock;
+ using time_point = std::chrono::time_point<clock_type>;
+
+ const int m_divider;
+
+ int m_count;
+
+ std::deque<time_point> m_timestamps;
+};
+
diff --git a/Click.h b/Click.h
index 7861d33..4af693e 100644
--- a/Click.h
+++ b/Click.h
@@ -3,6 +3,7 @@
#include <boost/signals2.hpp>
#include "config.h"
+#include "BPMDetect.h"
// Virtual base class
// Abstraction of BPM detection / setting
@@ -30,17 +31,20 @@ private:
class NoteClick: public Click
{
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_note(config.get_midi_note()),
+ m_detect(1)
{
+ m_detect.signal_bpm.connect([&](int bpm){signal_bpm(bpm);});
}
virtual ~NoteClick(){}
- // signals
- boost::signals2::signal<void()> signal_click;
-
// slots
void receive_note(int channel, int note, uint64_t timestamp)
{
@@ -48,19 +52,38 @@ public:
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 Click
{
public:
- ClockClick(){}
+ ClockClick(): m_detect(24)
+ {
+ m_detect.signal_bpm.connect([&](int bpm){signal_bpm(bpm);});
+ }
+
virtual ~ClockClick(){}
+
+ // signals
+ boost::signals2::signal<void(int)> signal_bpm;
+
+ // slots
+ void receive_clock()
+ {
+ m_detect.receive_event();
+ }
+
+private:
+ BPMDetect m_detect;
};
diff --git a/MIDI.cpp b/MIDI.cpp
index 5e625bd..dedd94a 100644
--- a/MIDI.cpp
+++ b/MIDI.cpp
@@ -148,6 +148,8 @@ void MIDI::process(snd_seq_event_t *ev)
{
log_cout << fmt::format("[{}] Unknown MIDI event: {}\n", timestamp_from_event(ev), ev->type) << std::endl;
}
+
+ signal_count_events();
}
void MIDI::flush()
diff --git a/MIDI.h b/MIDI.h
index 177fd59..8045684 100644
--- a/MIDI.h
+++ b/MIDI.h
@@ -19,6 +19,7 @@ public:
boost::signals2::signal<void(int, int, uint64_t)> signal_note;
boost::signals2::signal<void()> signal_active_sensing;
boost::signals2::signal<void()> signal_clock;
+ boost::signals2::signal<void()> signal_count_events;
int fd();
diff --git a/Makefile b/Makefile
index 2cebb56..faac1fe 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,8 @@ SRCS= \
debug.cpp \
cpuload.cpp \
log.cpp \
- Click.cpp
+ Click.cpp \
+ BPMDetect.cpp
HEADERS=$(SRCS:.cpp=.h)
@@ -25,7 +26,7 @@ $(TARGET): $(OBJS)
$(CXX) $^ -o $@ $(CXXLIBS)
%.o: %.cpp
- $(CXX) $(CXXFLAGS) -std=c++20 -O2 -g -Wall -o $@ -c $<
+ $(CXX) $(CXXFLAGS) -std=c++20 -O2 -g -Wall -Wpedantic -o $@ -c $<
clean:
rm -f $(TARGET) $(OBJS)
diff --git a/UI.cpp b/UI.cpp
index 4e063d0..0adb48b 100644
--- a/UI.cpp
+++ b/UI.cpp
@@ -15,44 +15,66 @@ const int midi_monitor_max_size = 3;
using namespace std::chrono_literals;
-static std::vector<std::string> mode_names{{
+static std::vector<std::string> mode_names{
"NoteClick", "Clock", "Internal"
-}};
+};
-UI::UI(Config& config):
- m_config(config),
- m_main_loops{},
- m_main_loops_checkpoint{},
- m_main_loops_timestamp{},
- m_active_sensing_timestamp{},
- m_midi_timestamp{}
+static std::vector<std::string> output_names{
+ "Off", "On"
+};
+
+static std::vector<std::string> active_sensing_names{
+ "Not Detected", "Detected"
+};
+
+IntervalCounter::IntervalCounter()
{
}
-int UI::get_main_loops_per_second()
+int IntervalCounter::get_count_per_second()
{
// calculate result
std::chrono::time_point<clock_type> now = clock_type::now();
- uint64_t diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_main_loops_timestamp).count();
- uint64_t loops_per_second = (diff_ms == 0 || m_main_loops_checkpoint == 0) ? 0 : ((m_main_loops - m_main_loops_checkpoint) * 1000 / diff_ms);
+
+ uint64_t diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(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_main_loops_timestamp = now;
- m_main_loops_checkpoint = m_main_loops;
+ m_checkpoint_timestamp = now;
+ m_count_checkpoint = m_count;
- return loops_per_second;
+ return count_per_second;
+}
+
+void IntervalCounter::count()
+{
+ m_count++;
+}
+
+UI::UI(Config& config):
+ m_config(config),
+ m_main_loops{},
+ m_midi_events{},
+ m_active_sensing_timestamp{},
+ m_midi_timestamp{},
+ m_note_bpm{},
+ m_clock_bpm{}
+{
}
void UI::draw()
{
std::vector<int> cpuloads = get_cpu_loads();
- int main_loops_per_second = get_main_loops_per_second();
+ 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 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 output = m_config.get_output();
- bool active_sensing_detected = (clock_type::now() - m_active_sensing_timestamp) < 2s;
+ int active_sensing_detected = (clock_type::now() - m_active_sensing_timestamp) < 2s ? 1 : 0;
// clear screen
std::cout << "\x1B[2J\x1B[H";
@@ -70,6 +92,8 @@ void UI::draw()
std::cout << std::endl;
std::cout << fmt::format("MIDI Note: {}/{}", channel, note);
std::cout << std::endl;
+ std::cout << "Audio Output: " << fmt::format(fg(fmt::color::crimson) | fmt::emphasis::bold, "{}", output_names[output]);
+ std::cout << std::endl;
std::cout << std::endl;
std::cout << "Status:" << std::endl;
@@ -80,7 +104,7 @@ void UI::draw()
int max = *std::max_element(cpuloads.begin(), cpuloads.end());
std::cout << fmt::format(", max. {:2}%", max) << std::endl;
- std::cout << " MIDI Activity: ";
+ std::cout << " MIDI Notes: ";
if (m_midi_monitor.empty()) {
std::cout << "--";
} else {
@@ -99,10 +123,11 @@ void UI::draw()
}
std::cout << std::endl;
std::cout << fmt::format(" MIDI Timestamp: {}", m_midi_timestamp) << std::endl;
- std::cout << fmt::format(" Active sensing: {}", active_sensing_detected) << std::endl;
- std::cout << " Clock: ____ BPM" << std::endl;
- std::cout << " Click: ____ BPM" << std::endl;
- std::cout << fmt::format(" Internal: {:3} BPM", bpm) << 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 << 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(" Main loops/s: {}", main_loops_per_second) << std::endl;
@@ -112,8 +137,12 @@ void UI::draw()
void UI::count_main_loops()
{
- ++m_main_loops;
- debug_cout << "DEBUG:" << m_main_loops << std::endl;
+ m_main_loops.count();
+}
+
+void UI::count_midi_events()
+{
+ m_midi_events.count();
}
void UI::slot_active_sensing()
@@ -131,3 +160,14 @@ void UI::slot_midi_note(int channel, int note, uint64_t timestamp)
m_midi_monitor.pop_back();
}
}
+
+void UI::slot_note_bpm(int bpm)
+{
+ m_note_bpm = bpm;
+}
+
+void UI::slot_clock_bpm(int bpm)
+{
+ m_clock_bpm = bpm;
+}
+
diff --git a/UI.h b/UI.h
index a3c56e0..db2f2cc 100644
--- a/UI.h
+++ b/UI.h
@@ -8,38 +8,56 @@
#include <boost/signals2.hpp>
+using clock_type = std::chrono::high_resolution_clock;
+
+struct IntervalCounter
+{
+public:
+ IntervalCounter();
+ int get_count_per_second();
+ void count();
+
+private:
+ uint64_t m_count{};
+ uint64_t m_count_checkpoint{};
+ std::chrono::time_point<clock_type> m_checkpoint_timestamp{};
+};
+
class UI
{
public:
- using clock_type = std::chrono::high_resolution_clock;
UI(Config& config);
void draw();
- // signals
+ // 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;
// slots
void count_main_loops();
+ void count_midi_events();
void slot_active_sensing();
void slot_midi_note(int channel, int note, uint64_t timestamp);
+ void slot_note_bpm(int bpm);
+ void slot_clock_bpm(int bpm);
private:
Config& m_config;
- int get_main_loops_per_second();
-
- uint64_t m_main_loops;
- uint64_t m_main_loops_checkpoint;
- std::chrono::time_point<clock_type> m_main_loops_timestamp;
+ IntervalCounter m_main_loops;
+ IntervalCounter m_midi_events;
std::chrono::time_point<clock_type> m_active_sensing_timestamp;
uint64_t m_midi_timestamp;
std::deque<std::pair<int,int>> m_midi_monitor;
+
+ int m_note_bpm;
+ int m_clock_bpm;
};
diff --git a/config.cpp b/config.cpp
index 04736ea..94e881d 100644
--- a/config.cpp
+++ b/config.cpp
@@ -31,6 +31,7 @@ void Config::recover()
m_midi_note = CLICK_NOTE;
m_bpm = default_bpm;
m_mode = default_mode;
+ m_output = default_output;
std::string config = Reichwein::File::getFile(config_filename);
@@ -53,6 +54,10 @@ void Config::recover()
m_mode = stoul(i.substr(5));
signal_mode(m_mode);
}
+ if (i.starts_with("output=")) {
+ m_output = stoul(i.substr(7));
+ signal_output(m_output);
+ }
}
} catch (const std::exception& ex) {
log_cout << "Config not found. Setting config to defaults." << std::endl;
@@ -61,10 +66,12 @@ void Config::recover()
void Config::persist()
{
- std::string config = fmt::format("midi_channel={}\n", m_midi_channel) +
+ std::string config =
+ fmt::format("midi_channel={}\n", m_midi_channel) +
fmt::format("midi_note={}\n", m_midi_note) +
fmt::format("bpm={}\n", m_bpm) +
- fmt::format("mode={}\n", m_mode);
+ fmt::format("mode={}\n", m_mode) +
+ fmt::format("output={}\n", m_output);
Reichwein::File::setFile(config_filename, config);
}
@@ -113,3 +120,14 @@ void Config::set_mode(int mode)
signal_mode(mode);
}
+int Config::get_output()
+{
+ return m_output;
+}
+
+void Config::set_output(int output)
+{
+ m_output = output;
+ signal_output(output);
+}
+
diff --git a/config.h b/config.h
index 5d2a1d0..e52e9f5 100644
--- a/config.h
+++ b/config.h
@@ -14,6 +14,7 @@ const int default_bpm = 120;
const int pcm_latency_us = 100000;
const int click_latency_frames = 10000;
const int default_mode = 0; // 0 = note, 1 = clock, 2 = internal
+const int default_output = 1; // 0 = off, 1 = on
class Config
{
@@ -26,6 +27,7 @@ public:
boost::signals2::signal<void(int)> signal_channel;
boost::signals2::signal<void(int)> signal_note;
boost::signals2::signal<void(int)> signal_bpm;
+ boost::signals2::signal<void(int)> signal_output;
int get_midi_channel();
void set_midi_channel(int channel);
@@ -39,6 +41,9 @@ public:
int get_mode();
void set_mode(int mode);
+ int get_output();
+ void set_output(int output);
+
void recover();
void persist();
@@ -47,4 +52,5 @@ private:
int m_midi_note;
int m_bpm;
int m_mode;
+ int m_output;
};
diff --git a/main.cpp b/main.cpp
index 337fa96..5c8d345 100644
--- a/main.cpp
+++ b/main.cpp
@@ -54,9 +54,6 @@ int main(void)
std::shared_ptr<NoteClick> note_click = std::make_shared<NoteClick>(config);
std::shared_ptr<InternalClick> internal_click = std::make_shared<InternalClick>(config);
- // Active Mode
- std::shared_ptr<Click> click = note_click;
-
MIDI midi;
PCM pcm;
UI ui(config);
@@ -77,11 +74,15 @@ int main(void)
//
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();