Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/audio/engine/analyzer_capture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,111 @@ void AnalyzerCapture::capture_output(const float* output, int count) {
}
}

bool AnalyzerCapture::register_pedal_analyzer(int node_id) {
if (node_id < 0) return false;

// Check if already registered
for (int i = 0; i < MAX_PEDAL_ANALYZERS; ++i) {
if (pedal_captures_[i].node_id_.load(std::memory_order_acquire) == node_id) {
return true;
}
}

// Find an empty slot
for (int i = 0; i < MAX_PEDAL_ANALYZERS; ++i) {
if (pedal_captures_[i].node_id_.load(std::memory_order_relaxed) == -1) {
int expected = -1;
if (pedal_captures_[i].node_id_.compare_exchange_strong(expected, node_id,
std::memory_order_acq_rel)) {
pedal_captures_[i].reset();
return true;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return false;
}

void AnalyzerCapture::unregister_pedal_analyzer(int node_id) {
if (node_id < 0) return;
for (int i = 0; i < MAX_PEDAL_ANALYZERS; ++i) {
int expected = node_id;
if (pedal_captures_[i].node_id_.compare_exchange_strong(expected, -1,
std::memory_order_acq_rel)) {
pedal_captures_[i].reset();
return;
}
}
}

uint64_t AnalyzerCapture::get_pedal_analyzer_sequence(int node_id) const {
for (int i = 0; i < MAX_PEDAL_ANALYZERS; ++i) {
if (pedal_captures_[i].node_id_.load(std::memory_order_acquire) == node_id) {
return pedal_captures_[i].sequence_.load(std::memory_order_acquire);
}
}
return 0;
}

bool AnalyzerCapture::copy_pedal_analyzer_snapshot(int node_id, float* input_dest,
float* output_dest, int sample_count) const {
if (!input_dest || !output_dest || sample_count <= 0) {
return false;
}
for (int i = 0; i < MAX_PEDAL_ANALYZERS; ++i) {
if (pedal_captures_[i].node_id_.load(std::memory_order_acquire) == node_id) {
const auto& pc = pedal_captures_[i];
const int count = std::min(sample_count, ANALYZER_FFT_SIZE);
std::lock_guard<std::mutex> lock(pc.mutex_);
const uint64_t seq = pc.sequence_.load(std::memory_order_relaxed);
if (seq == 0) {
return false;
}
std::memcpy(input_dest, pc.snapshot_input_.data(),
static_cast<size_t>(count) * sizeof(float));
std::memcpy(output_dest, pc.snapshot_output_.data(),
static_cast<size_t>(count) * sizeof(float));
return true;
}
}
return false;
}

void AnalyzerCapture::capture_pedal(int node_id, const float* input, const float* output,
int count) {
for (int i = 0; i < MAX_PEDAL_ANALYZERS; ++i) {
if (pedal_captures_[i].node_id_.load(std::memory_order_relaxed) == node_id) {
auto& pc = pedal_captures_[i];
int cap = pc.capture_index_.load(std::memory_order_relaxed);
for (int s = 0; s < count; ++s) {
pc.capture_input_[cap] = input[s];
pc.capture_output_[cap] = output[s];
cap = (cap + 1) & ANALYZER_FFT_MASK;
}
pc.capture_index_.store(cap, std::memory_order_relaxed);
Comment thread
Arbaaz123676 marked this conversation as resolved.

int current_samples = pc.samples_since_publish_.load(std::memory_order_relaxed) + count;
pc.samples_since_publish_.store(current_samples, std::memory_order_relaxed);
if (current_samples >= ANALYZER_HOP_SIZE) {
if (pc.mutex_.try_lock()) {
const int start = pc.capture_index_.load(std::memory_order_relaxed);
const int first_chunk = ANALYZER_FFT_SIZE - start;
std::memcpy(pc.snapshot_input_.data(), pc.capture_input_.data() + start,
static_cast<size_t>(first_chunk) * sizeof(float));
std::memcpy(pc.snapshot_input_.data() + first_chunk, pc.capture_input_.data(),
static_cast<size_t>(start) * sizeof(float));
std::memcpy(pc.snapshot_output_.data(), pc.capture_output_.data() + start,
static_cast<size_t>(first_chunk) * sizeof(float));
std::memcpy(pc.snapshot_output_.data() + first_chunk, pc.capture_output_.data(),
static_cast<size_t>(start) * sizeof(float));
pc.sequence_.fetch_add(1, std::memory_order_release);
pc.samples_since_publish_.store(0, std::memory_order_relaxed);
pc.mutex_.unlock();
}
}
break;
}
}
}

} // namespace Amplitron
33 changes: 33 additions & 0 deletions src/audio/engine/analyzer_capture.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class AnalyzerCapture : public IAnalyzerProvider {
static constexpr int ANALYZER_FFT_SIZE = 2048;
static constexpr int ANALYZER_FFT_MASK = ANALYZER_FFT_SIZE - 1;
static constexpr int ANALYZER_HOP_SIZE = 1024;
static constexpr int MAX_PEDAL_ANALYZERS = 4;

AnalyzerCapture();
~AnalyzerCapture() override = default;
Expand All @@ -27,10 +28,16 @@ class AnalyzerCapture : public IAnalyzerProvider {
uint64_t get_analyzer_sequence() const override;
bool copy_analyzer_snapshot(float* input_dest, float* output_dest,
int sample_count) const override;
bool register_pedal_analyzer(int node_id) override;
void unregister_pedal_analyzer(int node_id) override;
uint64_t get_pedal_analyzer_sequence(int node_id) const override;
bool copy_pedal_analyzer_snapshot(int node_id, float* input_dest, float* output_dest,
int sample_count) const override;

// Audio thread capture methods
void capture_input(const float* input, int count);
void capture_output(const float* output, int count);
void capture_pedal(int node_id, const float* input, const float* output, int count);

private:
std::atomic<bool> enabled_{false};
Expand All @@ -46,6 +53,32 @@ class AnalyzerCapture : public IAnalyzerProvider {
std::array<float, ANALYZER_FFT_SIZE> snapshot_input_{};
std::array<float, ANALYZER_FFT_SIZE> snapshot_output_{};
std::atomic<uint64_t> sequence_{0};

// Per-pedal analyzer captures
struct PedalCapture {
std::atomic<int> node_id_{-1};
std::array<float, ANALYZER_FFT_SIZE> capture_input_{};
std::array<float, ANALYZER_FFT_SIZE> capture_output_{};
std::atomic<int> capture_index_{0};
std::atomic<int> samples_since_publish_{0};

mutable std::mutex mutex_;
std::array<float, ANALYZER_FFT_SIZE> snapshot_input_{};
std::array<float, ANALYZER_FFT_SIZE> snapshot_output_{};
std::atomic<uint64_t> sequence_{0};

void reset() {
std::lock_guard<std::mutex> lock(mutex_);
capture_input_.fill(0.0f);
capture_output_.fill(0.0f);
snapshot_input_.fill(0.0f);
snapshot_output_.fill(0.0f);
capture_index_.store(0, std::memory_order_relaxed);
samples_since_publish_.store(0, std::memory_order_relaxed);
sequence_.store(0, std::memory_order_release);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
std::array<PedalCapture, MAX_PEDAL_ANALYZERS> pedal_captures_{};
};

} // namespace Amplitron
5 changes: 5 additions & 0 deletions src/audio/engine/audio_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ class AudioEngine : public IAudioEngine {
*/
bool copy_analyzer_snapshot(float* input_dest, float* output_dest,
int sample_count) const override;
bool register_pedal_analyzer(int node_id) override;
void unregister_pedal_analyzer(int node_id) override;
uint64_t get_pedal_analyzer_sequence(int node_id) const override;
bool copy_pedal_analyzer_snapshot(int node_id, float* input_dest, float* output_dest,
int sample_count) const override;

/**
* @brief Set the master input gain (enqueued to audio thread via SPSC queue).
Expand Down
18 changes: 18 additions & 0 deletions src/audio/engine/audio_engine_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,22 @@ bool AudioEngine::copy_analyzer_snapshot(float* input_dest, float* output_dest,
return analyzer_capture_->copy_analyzer_snapshot(input_dest, output_dest, sample_count);
}

bool AudioEngine::register_pedal_analyzer(int node_id) {
return analyzer_capture_->register_pedal_analyzer(node_id);
}

void AudioEngine::unregister_pedal_analyzer(int node_id) {
analyzer_capture_->unregister_pedal_analyzer(node_id);
}

uint64_t AudioEngine::get_pedal_analyzer_sequence(int node_id) const {
return analyzer_capture_->get_pedal_analyzer_sequence(node_id);
}

bool AudioEngine::copy_pedal_analyzer_snapshot(int node_id, float* input_dest, float* output_dest,
int sample_count) const {
return analyzer_capture_->copy_pedal_analyzer_snapshot(node_id, input_dest, output_dest,
sample_count);
}

} // namespace Amplitron
2 changes: 1 addition & 1 deletion src/audio/engine/audio_engine_process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void AudioEngine::process_audio(const float* input, float* output, int frame_cou

// Pass your mono/stereo buffers to the executor we built
audio_shadow_executor_->process(process_buffer_.data(), process_buffer_right_.data(),
frame_count);
frame_count, analyzer_capture_.get());
std::memcpy(process_buffer_.data(), process_buffer_right_.data(),
static_cast<size_t>(frame_count) * sizeof(float));
}
Expand Down
9 changes: 8 additions & 1 deletion src/audio/engine/audio_graph_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <algorithm>
#include <cstring>

#include "audio/engine/analyzer_capture.h"

namespace Amplitron {

AudioGraphExecutor::AudioGraphExecutor() {}
Expand Down Expand Up @@ -116,7 +118,8 @@ void AudioGraphExecutor::update_transport_state(float bpm) {
}
}

void AudioGraphExecutor::process(const float* input, float* output, int num_samples) {
void AudioGraphExecutor::process(const float* input, float* output, int num_samples,
AnalyzerCapture* capture) {
if (num_samples > max_block_size_) {
std::memset(output, 0, static_cast<size_t>(num_samples) * sizeof(float));
return;
Expand Down Expand Up @@ -163,6 +166,10 @@ void AudioGraphExecutor::process(const float* input, float* output, int num_samp
} else {
std::memcpy(node_output, node_input, num_samples * sizeof(float));
}

if (capture) {
capture->capture_pedal(step.node_id, node_input, node_output, num_samples);
}
}

// Accumulate/mix outputs from all explicit sink nodes into the final output buffer
Expand Down
5 changes: 4 additions & 1 deletion src/audio/engine/audio_graph_executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class PassthroughProcessor : public INodeProcessor {
}
};

class AnalyzerCapture;

class AudioGraphExecutor {
public:
friend class AudioEngine;
Expand Down Expand Up @@ -78,7 +80,8 @@ class AudioGraphExecutor {

// Hot-path processing (Strictly allocation-free and lock-free)
// Adjust the pedal->process signature if your pedals process strictly in-place
void process(const float* input, float* output, int num_samples);
void process(const float* input, float* output, int num_samples,
AnalyzerCapture* capture = nullptr);
void update_mixer_gain(int node_id, int pin_index, float gain);

std::shared_ptr<Effect> get_effect_by_node_id(int node_id) const {
Expand Down
5 changes: 5 additions & 0 deletions src/audio/engine/i_audio_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ class IAnalyzerProvider {
virtual uint64_t get_analyzer_sequence() const = 0;
virtual bool copy_analyzer_snapshot(float* input_dest, float* output_dest,
int sample_count) const = 0;
virtual bool register_pedal_analyzer(int node_id) = 0;
virtual void unregister_pedal_analyzer(int node_id) = 0;
virtual uint64_t get_pedal_analyzer_sequence(int node_id) const = 0;
virtual bool copy_pedal_analyzer_snapshot(int node_id, float* input_dest, float* output_dest,
int sample_count) const = 0;
};

/**
Expand Down
Loading
Loading