internal: format cpp
This commit is contained in:
parent
704db7b6a6
commit
8b1f2be27b
9 changed files with 307 additions and 251 deletions
22
.clang-format
Normal file
22
.clang-format
Normal 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
|
||||||
|
...
|
||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"editor.defaultFormatter": "theqtcompany.qt-qml"
|
"editor.defaultFormatter": "theqtcompany.qt-qml",
|
||||||
|
"[cpp]": {
|
||||||
|
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue