Skip to content

Commit fe1c799

Browse files
committed
Fix: Remove realtime safety violations from audio callback
Removed all heap allocations (std::vector::resize) and blocking I/O (console logging) from the real-time audio thread to prevent audio glitches and priority inversion.
1 parent 106d0e4 commit fe1c799

5 files changed

Lines changed: 23 additions & 12 deletions

File tree

src/audio/engine/audio_command_dispatcher.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,10 @@ void AudioCommandDispatcher::drain_commands(std::atomic<float>& input_gain,
101101
if (node_id >= 0 && node_id < static_cast<int>(dummy_effects.size())) {
102102
// Comments on node_id semantics: node_id is used as a 0-based linear index fallback
103103
// for the GUI and tests
104-
std::cerr << "[AudioCommandDispatcher] Node ID " << node_id
105-
<< " not found in executor or graph; falling back to dummy_effects index."
106-
<< std::endl;
104+
error_flag_.store(true, std::memory_order_release);
107105
return dummy_effects[node_id];
108106
}
109-
std::cerr << "[AudioCommandDispatcher] Node ID " << node_id
110-
<< " lookup failed completely." << std::endl;
107+
error_flag_.store(true, std::memory_order_release);
111108
return nullptr;
112109
};
113110

src/audio/engine/audio_command_dispatcher.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ class AudioCommandDispatcher {
3636
std::shared_ptr<AudioGraphExecutor>& executor, AudioGraph& main_graph,
3737
std::vector<std::shared_ptr<Effect>>& dummy_effects);
3838

39+
bool check_and_clear_error() {
40+
return error_flag_.exchange(false, std::memory_order_acq_rel);
41+
}
42+
3943
private:
4044
SPSCQueue<AudioCommand, 256> command_queue_;
45+
std::atomic<bool> error_flag_{false};
4146
};
4247

4348
} // namespace Amplitron

src/audio/engine/audio_engine_process.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ void AudioEngine::process_audio(const float* input, float* output, int frame_cou
1313
auto t_start = std::chrono::steady_clock::now();
1414

1515
if (frame_count > static_cast<int>(process_buffer_.size())) {
16-
process_buffer_.resize(frame_count, 0.0f);
17-
process_buffer_right_.resize(frame_count, 0.0f);
16+
if (output) {
17+
std::memset(output, 0, static_cast<size_t>(frame_count) * 2 * sizeof(float));
18+
}
19+
return;
1820
}
1921

2022
const bool analyzer_on = analyzer_capture_->is_analyzer_enabled();

tests/integration/test_audio_engine.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -531,15 +531,19 @@ TEST_F(AudioEngineTest, TunerTapSampleRateAndReset) {
531531
engine.clear_tuner_tap();
532532
}
533533

534-
TEST_F(AudioEngineTest, ProcessAudioResizesInternalBuffersWhenFrameCountExceedsCapacity) {
534+
TEST_F(AudioEngineTest, ProcessAudioZerosOutputWhenFrameCountExceedsCapacity) {
535535
// Default internal buffer capacity is 16384. Request a size larger than this.
536536
const int large_frame_count = 17000;
537537
std::vector<float> in(large_frame_count, 0.5f);
538-
std::vector<float> out(large_frame_count * 2, 0.0f);
538+
std::vector<float> out(large_frame_count * 2, 1.0f); // Pre-fill with 1.0f
539539

540540
engine.process_audio(in.data(), out.data(), large_frame_count);
541541

542-
// Check that the internal buffers resized accordingly
543-
ASSERT_GE(engine.test_process_buffer().size(), static_cast<size_t>(large_frame_count));
544-
ASSERT_GE(engine.test_process_buffer_right().size(), static_cast<size_t>(large_frame_count));
542+
// Check that the internal buffers did NOT resize (they stay at 16384)
543+
ASSERT_LT(engine.test_process_buffer().size(), static_cast<size_t>(large_frame_count));
544+
545+
// Check that the output was safely zeroed out
546+
for (float sample : out) {
547+
ASSERT_NEAR(sample, 0.0f, 1e-6f);
548+
}
545549
}

tests/unit/test_audio_command_dispatcher.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,7 @@ TEST(AudioCommandDispatcher_DrainCommands) {
9090
ASSERT_NEAR(fx1->get_mix(), 0.7f, 0.01f);
9191
ASSERT_NEAR(input_gain.load(), 0.2f, 0.01f);
9292
ASSERT_NEAR(output_gain.load(), 0.3f, 0.01f);
93+
94+
// Verify that the invalid node lookup safely set the atomic error flag instead of throwing/printing
95+
ASSERT_TRUE(dispatcher.check_and_clear_error());
9396
}

0 commit comments

Comments
 (0)