summaryrefslogtreecommitdiff
path: root/assets/cpp/beat-detector.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xassets/cpp/beat-detector.cpp298
1 files changed, 149 insertions, 149 deletions
diff --git a/assets/cpp/beat-detector.cpp b/assets/cpp/beat-detector.cpp
index 4eb9b48..787dc7b 100755
--- a/assets/cpp/beat-detector.cpp
+++ b/assets/cpp/beat-detector.cpp
@@ -1,79 +1,80 @@
#include <algorithm>
-#include <pipewire/pipewire.h>
-#include <spa/param/audio/format-utils.h>
-#include <spa/param/props.h>
+#include <atomic>
#include <aubio/aubio.h>
-#include <memory>
-#include <iostream>
-#include <fstream>
+#include <chrono>
#include <csignal>
-#include <atomic>
-#include <vector>
#include <cstring>
-#include <chrono>
+#include <fstream>
#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
#include <sstream>
-#include <thread>
-#include <cmath>
+#include <vector>
class EnhancedBeatDetector {
private:
static constexpr uint32_t SAMPLE_RATE = 44100;
static constexpr uint32_t CHANNELS = 1;
-
+
// PipeWire objects
pw_main_loop* main_loop_;
pw_context* context_;
pw_core* core_;
pw_stream* stream_;
-
+
// Aubio objects
std::unique_ptr<aubio_tempo_t, decltype(&del_aubio_tempo)> tempo_;
std::unique_ptr<fvec_t, decltype(&del_fvec)> input_buffer_;
std::unique_ptr<fvec_t, decltype(&del_fvec)> output_buffer_;
-
+
// Additional aubio objects for enhanced features
std::unique_ptr<aubio_onset_t, decltype(&del_aubio_onset)> onset_;
std::unique_ptr<aubio_pitch_t, decltype(&del_aubio_pitch)> pitch_;
std::unique_ptr<fvec_t, decltype(&del_fvec)> pitch_buffer_;
-
+
const uint32_t buf_size_;
const uint32_t fft_size_;
-
+
static std::atomic<bool> should_quit_;
static EnhancedBeatDetector* instance_;
-
+
// Enhanced features
std::ofstream log_file_;
bool enable_logging_;
bool enable_performance_stats_;
bool enable_pitch_detection_;
bool enable_visual_feedback_;
-
+
// Performance tracking
std::chrono::high_resolution_clock::time_point last_process_time_;
std::vector<double> process_times_;
uint64_t total_beats_;
uint64_t total_onsets_;
std::chrono::steady_clock::time_point start_time_;
-
+
// Beat analysis
std::vector<float> recent_bpms_;
static constexpr size_t BPM_HISTORY_SIZE = 10;
float last_bpm_;
std::chrono::steady_clock::time_point last_beat_time_;
-
+
// Useless Visual feedback
std::string generate_beat_visual(float bpm, bool is_beat) {
- if (!enable_visual_feedback_) return "";
-
+ if (!enable_visual_feedback_)
+ return "";
+
std::stringstream ss;
if (is_beat) {
// Useless Animated beat indicator based on BPM intensity
int intensity = static_cast<int>(std::min(bpm / 20.0f, 10.0f));
ss << "\r ";
- for (int i = 0; i < intensity; ++i) ss << "█";
- for (int i = intensity; i < 10; ++i) ss << "░";
+ for (int i = 0; i < intensity; ++i)
+ ss << "█";
+ for (int i = intensity; i < 10; ++i)
+ ss << "░";
ss << " BPM: " << std::fixed << std::setprecision(1) << bpm;
ss << " | Avg: " << get_average_bpm();
}
@@ -81,11 +82,8 @@ private:
}
public:
- explicit EnhancedBeatDetector(uint32_t buf_size = 512,
- bool enable_logging = true,
- bool enable_performance_stats = true,
- bool enable_pitch_detection = false,
- bool enable_visual_feedback = true)
+ explicit EnhancedBeatDetector(uint32_t buf_size = 512, bool enable_logging = true,
+ bool enable_performance_stats = true, bool enable_pitch_detection = false, bool enable_visual_feedback = true)
: main_loop_(nullptr)
, context_(nullptr)
, core_(nullptr)
@@ -104,8 +102,7 @@ public:
, enable_visual_feedback_(enable_visual_feedback)
, total_beats_(0)
, total_onsets_(0)
- , last_bpm_(0.0f)
- {
+ , last_bpm_(0.0f) {
instance_ = this;
recent_bpms_.reserve(BPM_HISTORY_SIZE);
if (enable_performance_stats_) {
@@ -113,20 +110,20 @@ public:
}
initialize();
}
-
+
~EnhancedBeatDetector() {
print_final_stats();
cleanup();
instance_ = nullptr;
}
-
+
// Delete copy constructor and assignment operator
EnhancedBeatDetector(const EnhancedBeatDetector&) = delete;
EnhancedBeatDetector& operator=(const EnhancedBeatDetector&) = delete;
-
+
bool initialize() {
start_time_ = std::chrono::steady_clock::now();
-
+
// Useless Initialize logging (actually useful)
if (enable_logging_) {
auto now = std::chrono::system_clock::now();
@@ -135,55 +132,56 @@ public:
filename << "beat_log_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".txt";
log_file_.open(filename.str());
if (log_file_.is_open()) {
- log_file_ << "# Beat Detection Log - " << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S") << "\n";
+ log_file_ << "# Beat Detection Log - " << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S")
+ << "\n";
log_file_ << "# Timestamp,BPM,Onset,Pitch(Hz),ProcessTime(ms)\n";
std::cout << " Logging to: " << filename.str() << std::endl;
}
}
-
+
// Initialize PipeWire
pw_init(nullptr, nullptr);
-
+
main_loop_ = pw_main_loop_new(nullptr);
if (!main_loop_) {
std::cerr << " Failed to create main loop" << std::endl;
return false;
}
-
+
context_ = pw_context_new(pw_main_loop_get_loop(main_loop_), nullptr, 0);
if (!context_) {
std::cerr << " Failed to create context" << std::endl;
return false;
}
-
+
core_ = pw_context_connect(context_, nullptr, 0);
if (!core_) {
std::cerr << " Failed to connect to PipeWire" << std::endl;
return false;
}
-
+
// Initialize Aubio objects
tempo_.reset(new_aubio_tempo("default", fft_size_, buf_size_, SAMPLE_RATE));
if (!tempo_) {
std::cerr << " Failed to create aubio tempo detector" << std::endl;
return false;
}
-
+
input_buffer_.reset(new_fvec(buf_size_));
output_buffer_.reset(new_fvec(1));
-
+
if (!input_buffer_ || !output_buffer_) {
std::cerr << " Failed to create aubio buffers" << std::endl;
return false;
}
-
+
// Initialize onset detection
onset_.reset(new_aubio_onset("default", fft_size_, buf_size_, SAMPLE_RATE));
if (!onset_) {
std::cerr << " Failed to create aubio onset detector" << std::endl;
return false;
}
-
+
// Initialize pitch detection if enabled
if (enable_pitch_detection_) {
pitch_.reset(new_aubio_pitch("default", fft_size_, buf_size_, SAMPLE_RATE));
@@ -194,24 +192,25 @@ public:
}
aubio_pitch_set_unit(pitch_.get(), "Hz");
}
-
+
return setup_stream();
}
-
+
void run() {
- if (!main_loop_) return;
-
+ if (!main_loop_)
+ return;
+
print_startup_info();
pw_main_loop_run(main_loop_);
}
-
+
void stop() {
should_quit_ = true;
if (main_loop_) {
pw_main_loop_quit(main_loop_);
}
}
-
+
static void signal_handler(int sig) {
if (instance_) {
std::cout << "\n Received signal " << sig << ", stopping gracefullllly..." << std::endl;
@@ -230,44 +229,48 @@ private:
std::cout << " 󰗅 Pitch detection: " << (enable_pitch_detection_ ? " " : "") << std::endl;
std::cout << "\n Listening for beats... Press Ctrl+C to stop.\n" << std::endl;
}
-
+
void print_final_stats() {
- if (!enable_performance_stats_) return;
-
+ if (!enable_performance_stats_)
+ return;
+
auto end_time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time_);
-
+
std::cout << "\n Final Statistics:" << std::endl;
std::cout << " 󱎫 Total runtime: " << duration.count() << " seconds" << std::endl;
std::cout << "  Total beats detected: " << total_beats_ << std::endl;
std::cout << "  Total onsets detected: " << total_onsets_ << std::endl;
-
+
if (!process_times_.empty()) {
double avg_time = 0;
- for (double t : process_times_) avg_time += t;
+ for (double t : process_times_)
+ avg_time += t;
avg_time /= process_times_.size();
-
+
auto max_time = *std::max_element(process_times_.begin(), process_times_.end());
auto min_time = *std::min_element(process_times_.begin(), process_times_.end());
-
- std::cout << " ⚡ Average processing time: " << std::fixed << std::setprecision(3)
- << avg_time << " ms" << std::endl;
+
+ std::cout << " ⚡ Average processing time: " << std::fixed << std::setprecision(3) << avg_time << " ms"
+ << std::endl;
std::cout << " 📈 Max processing time: " << max_time << " ms" << std::endl;
std::cout << " 📉 Min processing time: " << min_time << " ms" << std::endl;
}
-
+
if (!recent_bpms_.empty()) {
std::cout << " 󰝚 Final average BPM: " << get_average_bpm() << std::endl;
}
}
-
+
float get_average_bpm() const {
- if (recent_bpms_.empty()) return 0.0f;
+ if (recent_bpms_.empty())
+ return 0.0f;
float sum = 0;
- for (float bpm : recent_bpms_) sum += bpm;
+ for (float bpm : recent_bpms_)
+ sum += bpm;
return sum / recent_bpms_.size();
}
-
+
bool setup_stream() {
// Stream events
static const pw_stream_events stream_events = {
@@ -284,125 +287,125 @@ private:
.command = nullptr,
.trigger_done = nullptr,
};
-
- stream_ = pw_stream_new_simple(
- pw_main_loop_get_loop(main_loop_),
- "enhanced-beat-detector",
+
+ stream_ = pw_stream_new_simple(pw_main_loop_get_loop(main_loop_), "enhanced-beat-detector",
pw_properties_new(
- PW_KEY_MEDIA_TYPE, "Audio",
- PW_KEY_MEDIA_CATEGORY, "Capture",
- PW_KEY_MEDIA_ROLE, "Music",
- nullptr
- ),
- &stream_events,
- this
- );
-
+ PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr),
+ &stream_events, this);
+
if (!stream_) {
std::cerr << " Failed to create stream" << std::endl;
return false;
}
-
+
// Audio format parameters
uint8_t buffer[1024];
spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
-
+
struct spa_audio_info_raw audio_info = {};
audio_info.format = SPA_AUDIO_FORMAT_F32_LE;
audio_info.channels = CHANNELS;
audio_info.rate = SAMPLE_RATE;
audio_info.flags = 0;
-
+
const spa_pod* params[1];
params[0] = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_EnumFormat, &audio_info);
-
- if (pw_stream_connect(stream_,
- PW_DIRECTION_INPUT,
- PW_ID_ANY,
- static_cast<pw_stream_flags>(
- PW_STREAM_FLAG_AUTOCONNECT |
- PW_STREAM_FLAG_MAP_BUFFERS |
- PW_STREAM_FLAG_RT_PROCESS),
- params, 1) < 0) {
+
+ if (pw_stream_connect(stream_, PW_DIRECTION_INPUT, PW_ID_ANY,
+ static_cast<pw_stream_flags>(
+ PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
+ params, 1) < 0) {
std::cerr << " Failed to connect stream" << std::endl;
return false;
}
-
+
return true;
}
-
- static void on_state_changed(void* userdata, enum pw_stream_state /* old_state */,
- enum pw_stream_state state, const char* error) {
+
+ static void on_state_changed(
+ void* userdata, enum pw_stream_state /* old_state */, enum pw_stream_state state, const char* error) {
auto* detector = static_cast<EnhancedBeatDetector*>(userdata);
-
+
const char* state_emoji = "󰑓 ";
switch (state) {
- case PW_STREAM_STATE_CONNECTING: state_emoji = "󰄙 "; break;
- case PW_STREAM_STATE_PAUSED: state_emoji = ""; break;
- case PW_STREAM_STATE_STREAMING: state_emoji = "󰝚 "; break;
- case PW_STREAM_STATE_ERROR: state_emoji = ""; break;
- case PW_STREAM_STATE_UNCONNECTED: state_emoji = " "; break;
+ case PW_STREAM_STATE_CONNECTING:
+ state_emoji = "󰄙 ";
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ state_emoji = "";
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ state_emoji = "󰝚 ";
+ break;
+ case PW_STREAM_STATE_ERROR:
+ state_emoji = "";
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ state_emoji = " ";
+ break;
}
-
+
std::cout << state_emoji << " Stream state: " << pw_stream_state_as_string(state) << std::endl;
-
+
if (state == PW_STREAM_STATE_ERROR) {
std::cerr << " Stream error: " << (error ? error : "unknown") << std::endl;
detector->stop();
}
}
-
+
static void on_process(void* userdata) {
auto* detector = static_cast<EnhancedBeatDetector*>(userdata);
detector->process_audio();
}
-
+
void process_audio() {
- if (should_quit_) return;
-
+ if (should_quit_)
+ return;
+
auto process_start = std::chrono::high_resolution_clock::now();
-
+
pw_buffer* buffer = pw_stream_dequeue_buffer(stream_);
- if (!buffer) return;
-
+ if (!buffer)
+ return;
+
spa_buffer* spa_buf = buffer->buffer;
if (!spa_buf->datas[0].data) {
pw_stream_queue_buffer(stream_, buffer);
return;
}
-
+
const float* audio_data = static_cast<const float*>(spa_buf->datas[0].data);
const uint32_t n_samples = spa_buf->datas[0].chunk->size / sizeof(float);
-
+
// Process in chunks
for (uint32_t offset = 0; offset + buf_size_ <= n_samples; offset += buf_size_) {
std::memcpy(input_buffer_->data, audio_data + offset, buf_size_ * sizeof(float));
-
+
// Beat detection
aubio_tempo_do(tempo_.get(), input_buffer_.get(), output_buffer_.get());
bool is_beat = output_buffer_->data[0] != 0.0f;
-
+
// Onset detection
aubio_onset_do(onset_.get(), input_buffer_.get(), output_buffer_.get());
bool is_onset = output_buffer_->data[0] != 0.0f;
-
+
float pitch_hz = 0.0f;
if (enable_pitch_detection_) {
aubio_pitch_do(pitch_.get(), input_buffer_.get(), pitch_buffer_.get());
pitch_hz = pitch_buffer_->data[0];
}
-
+
if (is_beat) {
total_beats_++;
last_bpm_ = aubio_tempo_get_bpm(tempo_.get());
last_beat_time_ = std::chrono::steady_clock::now();
-
+
// Update BPM history
recent_bpms_.push_back(last_bpm_);
if (recent_bpms_.size() > BPM_HISTORY_SIZE) {
recent_bpms_.erase(recent_bpms_.begin());
}
-
+
// Visual feedback
if (enable_visual_feedback_) {
std::cout << generate_beat_visual(last_bpm_, true) << std::flush;
@@ -410,84 +413,81 @@ private:
std::cout << " BPM: " << std::fixed << std::setprecision(1) << last_bpm_ << std::endl;
}
}
-
+
if (is_onset) {
total_onsets_++;
}
-
+
// Logging
if (enable_logging_ && log_file_.is_open() && (is_beat || is_onset)) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
- auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
- now.time_since_epoch()) % 1000;
-
- log_file_ << std::put_time(std::localtime(&time_t), "%H:%M:%S")
- << "." << std::setfill('0') << std::setw(3) << ms.count() << ","
- << (is_beat ? last_bpm_ : 0) << ","
- << (is_onset ? 1 : 0) << ","
- << pitch_hz << ",";
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
+
+ log_file_ << std::put_time(std::localtime(&time_t), "%H:%M:%S") << "." << std::setfill('0')
+ << std::setw(3) << ms.count() << "," << (is_beat ? last_bpm_ : 0) << "," << (is_onset ? 1 : 0)
+ << "," << pitch_hz << ",";
}
}
-
+
pw_stream_queue_buffer(stream_, buffer);
-
+
// Performance tracking
if (enable_performance_stats_) {
auto process_end = std::chrono::high_resolution_clock::now();
auto process_time = std::chrono::duration<double, std::milli>(process_end - process_start).count();
-
+
if (log_file_.is_open() && (total_beats_ > 0 || total_onsets_ > 0)) {
log_file_ << std::fixed << std::setprecision(3) << process_time << "\n";
}
-
+
if (process_times_.size() < 1000) {
process_times_.push_back(process_time);
}
}
}
-
+
void cleanup() {
if (log_file_.is_open()) {
log_file_.close();
}
-
+
if (stream_) {
pw_stream_destroy(stream_);
stream_ = nullptr;
}
-
+
if (core_) {
pw_core_disconnect(core_);
core_ = nullptr;
}
-
+
if (context_) {
pw_context_destroy(context_);
context_ = nullptr;
}
-
+
if (main_loop_) {
pw_main_loop_destroy(main_loop_);
main_loop_ = nullptr;
}
-
+
tempo_.reset();
input_buffer_.reset();
output_buffer_.reset();
onset_.reset();
pitch_.reset();
pitch_buffer_.reset();
-
+
pw_deinit();
-
+
std::cout << "\n Cleanup complete - All resources freed!" << std::endl;
}
};
// Static member definitions
-std::atomic<bool> EnhancedBeatDetector::should_quit_{false};
-EnhancedBeatDetector* EnhancedBeatDetector::instance_{nullptr};
+std::atomic<bool> EnhancedBeatDetector::should_quit_{ false };
+EnhancedBeatDetector* EnhancedBeatDetector::instance_{ nullptr };
void print_usage() {
std::cout << " Beat Detector Usage:" << std::endl;
@@ -511,10 +511,10 @@ int main(int argc, char* argv[]) {
bool enable_performance_stats = true;
bool enable_pitch_detection = false;
bool enable_visual_feedback = true;
-
+
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
-
+
if (arg == "--help" || arg == "-h") {
print_usage();
return 0;
@@ -544,20 +544,20 @@ int main(int argc, char* argv[]) {
return 1;
}
}
-
+
// Set up signal handling
std::signal(SIGINT, EnhancedBeatDetector::signal_handler);
std::signal(SIGTERM, EnhancedBeatDetector::signal_handler);
-
+
try {
- EnhancedBeatDetector detector(buffer_size, enable_logging, enable_performance_stats,
- enable_pitch_detection, enable_visual_feedback);
+ EnhancedBeatDetector detector(
+ buffer_size, enable_logging, enable_performance_stats, enable_pitch_detection, enable_visual_feedback);
detector.run();
} catch (const std::exception& e) {
std::cerr << " Error: " << e.what() << std::endl;
return 1;
}
-
+
return 0;
}