internal: format cpp

This commit is contained in:
2 * r + 2 * t 2025-09-01 17:41:26 +10:00
parent 704db7b6a6
commit 8b1f2be27b
9 changed files with 307 additions and 251 deletions

22
.clang-format Normal file
View file

@ -0,0 +1,22 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 120
---
Language: Cpp
DerivePointerAlignment: false
PointerAlignment: Left
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: None
BinPackArguments: true
BreakBeforeBraces: Attach
BreakConstructorInitializers: BeforeComma
Cpp11BracedListStyle: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: Always
IndentAccessModifiers: false
IndentCaseLabels: false
...

View file

@ -1,3 +1,6 @@
{ {
"editor.defaultFormatter": "theqtcompany.qt-qml" "editor.defaultFormatter": "theqtcompany.qt-qml",
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
}
} }

View file

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

View file

@ -1,14 +1,14 @@
#include "cachingimagemanager.hpp" #include "cachingimagemanager.hpp"
#include <QCryptographicHash>
#include <QDir>
#include <QFile>
#include <QImageReader>
#include <QObject> #include <QObject>
#include <QPainter>
#include <QThreadPool>
#include <QtQuick/QQuickItem> #include <QtQuick/QQuickItem>
#include <QtQuick/QQuickWindow> #include <QtQuick/QQuickWindow>
#include <QCryptographicHash>
#include <QThreadPool>
#include <QFile>
#include <QDir>
#include <QPainter>
#include <QImageReader>
qreal CachingImageManager::effectiveScale() const { qreal CachingImageManager::effectiveScale() const {
if (m_item && m_item->window()) { if (m_item && m_item->window()) {
@ -49,8 +49,12 @@ void CachingImageManager::setItem(QQuickItem* item) {
emit itemChanged(); emit itemChanged();
if (item) { if (item) {
m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() { updateSource(); }); m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() {
m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() { updateSource(); }); updateSource();
});
m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() {
updateSource();
});
updateSource(); updateSource();
} }
} }
@ -104,49 +108,54 @@ void CachingImageManager::updateSource(const QString& path) {
QThreadPool::globalInstance()->start([path, self] { QThreadPool::globalInstance()->start([path, self] {
const QString sha = self->sha256sum(path); const QString sha = self->sha256sum(path);
QMetaObject::invokeMethod(self, [path, sha, self]() { QMetaObject::invokeMethod(
if (!self || self->m_path != path) { self,
// Object is destroyed or path has changed, ignore [path, sha, self]() {
return; if (!self || self->m_path != path) {
} // Object is destroyed or path has changed, ignore
return;
}
const QSize size = self->effectiveSize(); const QSize size = self->effectiveSize();
if (!self->m_item || !size.width() || !size.height()) { if (!self->m_item || !size.width() || !size.height()) {
return; return;
} }
const QString fillMode = self->m_item->property("fillMode").toString(); const QString fillMode = self->m_item->property("fillMode").toString();
const QString filename = QString("%1@%2x%3-%4.png") // clang-format off
.arg(sha).arg(size.width()).arg(size.height()) const QString filename = QString("%1@%2x%3-%4.png")
.arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch"); .arg(sha).arg(size.width()).arg(size.height())
.arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch");
// clang-format on
const QUrl cache = self->m_cacheDir.resolved(QUrl(filename)); const QUrl cache = self->m_cacheDir.resolved(QUrl(filename));
if (self->m_cachePath == cache) { if (self->m_cachePath == cache) {
return; return;
} }
self->m_cachePath = cache; self->m_cachePath = cache;
emit self->cachePathChanged(); emit self->cachePathChanged();
if (!cache.isLocalFile()) { if (!cache.isLocalFile()) {
qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file"; qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file";
return; return;
} }
const QImageReader reader(cache.toLocalFile()); const QImageReader reader(cache.toLocalFile());
if (reader.canRead()) { if (reader.canRead()) {
self->m_item->setProperty("source", cache); self->m_item->setProperty("source", cache);
} else { } else {
self->m_item->setProperty("source", QUrl::fromLocalFile(path)); self->m_item->setProperty("source", QUrl::fromLocalFile(path));
self->createCache(path, cache.toLocalFile(), fillMode, size); self->createCache(path, cache.toLocalFile(), fillMode, size);
} }
// Clear current running sha if same // Clear current running sha if same
if (self->m_shaPath == path) { if (self->m_shaPath == path) {
self->m_shaPath = QString(); self->m_shaPath = QString();
} }
}, Qt::QueuedConnection); },
Qt::QueuedConnection);
}); });
} }
@ -154,7 +163,8 @@ QUrl CachingImageManager::cachePath() const {
return m_cachePath; return m_cachePath;
} }
void CachingImageManager::createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const { void CachingImageManager::createCache(
const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const {
QThreadPool::globalInstance()->start([path, cache, fillMode, size] { QThreadPool::globalInstance()->start([path, cache, fillMode, size] {
QImage image(path); QImage image(path);

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <QtQuick/QQuickItem>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <QtQuick/QQuickItem>
class CachingImageManager : public QObject { class CachingImageManager : public QObject {
Q_OBJECT Q_OBJECT
@ -15,7 +15,8 @@ class CachingImageManager : public QObject {
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
public: public:
explicit CachingImageManager(QObject* parent = nullptr): QObject(parent) {} explicit CachingImageManager(QObject* parent = nullptr)
: QObject(parent) {}
[[nodiscard]] QQuickItem* item() const; [[nodiscard]] QQuickItem* item() const;
void setItem(QQuickItem* item); void setItem(QQuickItem* item);

View file

@ -1,12 +1,12 @@
#include "cutils.hpp" #include "cutils.hpp"
#include <QDir>
#include <QObject> #include <QObject>
#include <QtQuick/QQuickWindow> #include <QQmlEngine>
#include <QThreadPool>
#include <QtQuick/QQuickItem> #include <QtQuick/QQuickItem>
#include <QtQuick/QQuickItemGrabResult> #include <QtQuick/QQuickItemGrabResult>
#include <QThreadPool> #include <QtQuick/QQuickWindow>
#include <QQmlEngine>
#include <QDir>
void CUtils::saveItem(QQuickItem* target, const QUrl& path) { void CUtils::saveItem(QQuickItem* target, const QUrl& path) {
this->saveItem(target, path, QRect(), QJSValue(), QJSValue()); this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
@ -47,7 +47,8 @@ void CUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
auto scaledRect = rect; auto scaledRect = rect;
const qreal scale = target->window()->devicePixelRatio(); const qreal scale = target->window()->devicePixelRatio();
if (rect.isValid() && scale != 1.0) { if (rect.isValid() && scale != 1.0) {
scaledRect = QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect(); scaledRect =
QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect();
} }
const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage(); const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage();
@ -65,21 +66,24 @@ void CUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
const QString parent = QFileInfo(file).absolutePath(); const QString parent = QFileInfo(file).absolutePath();
const bool success = QDir().mkpath(parent) && image.save(file); const bool success = QDir().mkpath(parent) && image.save(file);
QMetaObject::invokeMethod(this, [file, success, path, onSaved, onFailed, this]() { QMetaObject::invokeMethod(
if (success) { this,
if (onSaved.isCallable()) { [file, success, path, onSaved, onFailed, this]() {
onSaved.call({ QJSValue(file), qmlEngine(this)->toScriptValue(QVariant::fromValue(path)) }); if (success) {
if (onSaved.isCallable()) {
onSaved.call(
{ QJSValue(file), qmlEngine(this)->toScriptValue(QVariant::fromValue(path)) });
}
} else {
qWarning() << "CUtils::saveItem: failed to save" << path;
if (onFailed.isCallable()) {
onFailed.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(path)) });
}
} }
} else { },
qWarning() << "CUtils::saveItem: failed to save" << path; Qt::QueuedConnection);
if (onFailed.isCallable()) {
onFailed.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(path)) });
}
}
}, Qt::QueuedConnection);
}); });
} });
);
} }
bool CUtils::copyFile(const QUrl& source, const QUrl& target) const { bool CUtils::copyFile(const QUrl& source, const QUrl& target) const {
@ -120,21 +124,23 @@ void CUtils::getDominantColour(QQuickItem* item, int rescaleSize, QJSValue callb
const QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage(); const QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, QObject::connect(
[grabResult, rescaleSize, callback, this]() { grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, rescaleSize, callback, this]() {
const QImage image = grabResult->image(); const QImage image = grabResult->image();
QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() { QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() {
const QColor color = this->findDominantColour(image, rescaleSize); const QColor color = this->findDominantColour(image, rescaleSize);
if (callback.isCallable()) { if (callback.isCallable()) {
QMetaObject::invokeMethod(this, [color, callback, this]() { QMetaObject::invokeMethod(
callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) }); this,
}, Qt::QueuedConnection); [color, callback, this]() {
callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) });
},
Qt::QueuedConnection);
} }
}); });
} });
);
} }
void CUtils::getDominantColour(const QString& path, QJSValue callback) { void CUtils::getDominantColour(const QString& path, QJSValue callback) {
@ -158,9 +164,12 @@ void CUtils::getDominantColour(const QString& path, int rescaleSize, QJSValue ca
const QColor color = this->findDominantColour(image, rescaleSize); const QColor color = this->findDominantColour(image, rescaleSize);
if (callback.isCallable()) { if (callback.isCallable()) {
QMetaObject::invokeMethod(this, [color, callback, this]() { QMetaObject::invokeMethod(
callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) }); this,
}, Qt::QueuedConnection); [color, callback, this]() {
callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) });
},
Qt::QueuedConnection);
} }
}); });
} }
@ -234,21 +243,23 @@ void CUtils::getAverageLuminance(QQuickItem* item, int rescaleSize, QJSValue cal
const QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage(); const QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, QObject::connect(
[grabResult, rescaleSize, callback, this]() { grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, rescaleSize, callback, this]() {
const QImage image = grabResult->image(); const QImage image = grabResult->image();
QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() { QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() {
const qreal luminance = this->findAverageLuminance(image, rescaleSize); const qreal luminance = this->findAverageLuminance(image, rescaleSize);
if (callback.isCallable()) { if (callback.isCallable()) {
QMetaObject::invokeMethod(this, [luminance, callback]() { QMetaObject::invokeMethod(
callback.call({ QJSValue(luminance) }); this,
}, Qt::QueuedConnection); [luminance, callback]() {
callback.call({ QJSValue(luminance) });
},
Qt::QueuedConnection);
} }
}); });
} });
);
} }
void CUtils::getAverageLuminance(const QString& path, QJSValue callback) { void CUtils::getAverageLuminance(const QString& path, QJSValue callback) {
@ -272,9 +283,12 @@ void CUtils::getAverageLuminance(const QString& path, int rescaleSize, QJSValue
const qreal luminance = this->findAverageLuminance(image, rescaleSize); const qreal luminance = this->findAverageLuminance(image, rescaleSize);
if (callback.isCallable()) { if (callback.isCallable()) {
QMetaObject::invokeMethod(this, [luminance, callback]() { QMetaObject::invokeMethod(
callback.call({ QJSValue(luminance) }); this,
}, Qt::QueuedConnection); [luminance, callback]() {
callback.call({ QJSValue(luminance) });
},
Qt::QueuedConnection);
} }
}); });
} }

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <QtQuick/QQuickItem>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <QtQuick/QQuickItem>
class CUtils : public QObject { class CUtils : public QObject {
Q_OBJECT Q_OBJECT
@ -15,7 +15,8 @@ public:
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(
QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target) const; Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target) const;
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite) const; Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite) const;

View file

@ -1,12 +1,12 @@
#include "filesystemmodel.hpp" #include "filesystemmodel.hpp"
#include <QObject>
#include <qqmlintegration.h>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QFileInfo>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QFileInfo>
#include <QImageReader> #include <QImageReader>
#include <QObject>
#include <qqmlintegration.h>
int FileSystemModel::rowCount(const QModelIndex& parent) const { int FileSystemModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) { if (parent != QModelIndex()) {
@ -23,7 +23,7 @@ QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
} }
QHash<int, QByteArray> FileSystemModel::roleNames() const { QHash<int, QByteArray> FileSystemModel::roleNames() const {
return {{ Qt::UserRole, "modelData"}}; return { { Qt::UserRole, "modelData" } };
} }
QString FileSystemModel::path() const { QString FileSystemModel::path() const {

View file

@ -1,12 +1,12 @@
#pragma once #pragma once
#include <QAbstractListModel>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QImageReader>
#include <QObject> #include <QObject>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <QAbstractListModel>
#include <QFileSystemWatcher>
#include <QFileInfo>
#include <QDir>
#include <QImageReader>
class FileSystemEntry : public QObject { class FileSystemEntry : public QObject {
Q_OBJECT Q_OBJECT
@ -23,7 +23,10 @@ class FileSystemEntry : public QObject {
public: public:
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr) explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr)
: QObject(parent), m_fileInfo(QFileInfo(path)), m_path(path), m_relativePath(relativePath) {} : QObject(parent)
, m_fileInfo(QFileInfo(path))
, m_path(path)
, m_relativePath(relativePath) {}
QString path() const { return m_path; }; QString path() const { return m_path; };
QString relativePath() const { return m_relativePath; }; QString relativePath() const { return m_relativePath; };
@ -71,10 +74,12 @@ public:
Q_ENUM(Filter) Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr) explicit FileSystemModel(QObject* parent = nullptr)
: QAbstractListModel(parent), m_recursive(true), m_filter(NoFilter) { : QAbstractListModel(parent)
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive); , m_recursive(true)
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntries); , m_filter(NoFilter) {
} connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntries);
}
int rowCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;