Skip to content

Commit 27ccf3a

Browse files
Sin-telroderickvd
andauthored
fix(asio): FL Studio ASIO quirk (#1077)
Co-authored-by: Sintel <17052580+Sin-tel@users.noreply.github.com> Co-authored-by: Roderick van Domburg <roderick@vandomburg.net>
1 parent 37ecc11 commit 27ccf3a

File tree

5 files changed

+20
-9
lines changed

5 files changed

+20
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7272
- **ALSA**: Format selection to probe hardware endianness instead of assuming native byte order.
7373
- **ALSA**: Data race in stream shutdown.
7474
- **ASIO**: Handling for `kAsioResetRequest` message to prevent driver UI becoming unresponsive.
75+
- **ASIO**: Buffer silencing logic to work with non-conformant drivers (e.g., FL Studio ASIO).
7576
- **CoreAudio**: Timestamp accuracy.
7677
- **CoreAudio**: Segfaults when enumerating devices.
7778
- **CoreAudio**: Undefined behavior related to null pointers and aligned reads.

asio-sys/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111
- Fixed docs.rs documentation build by generating stub bindings when building for docs.rs
12+
- Fixed buffer switch detection to work correctly with non-conformant ASIO drivers
1213

1314
## [0.2.3] - 2025-12-12
1415

asio-sys/src/bindings/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::ffi::{CStr, CString};
99
use std::os::raw::{c_char, c_double, c_void};
1010
use std::ptr::null_mut;
1111
use std::sync::{
12-
atomic::{AtomicBool, Ordering},
12+
atomic::{AtomicBool, AtomicU32, Ordering},
1313
Arc, Mutex, MutexGuard, Weak,
1414
};
1515

@@ -100,6 +100,7 @@ pub struct SampleRate {
100100
pub struct CallbackInfo {
101101
pub buffer_index: i32,
102102
pub system_time: ai::ASIOTimeStamp,
103+
pub callback_flag: u32,
103104
}
104105

105106
/// Holds the pointer to the callbacks that come from cpal
@@ -319,6 +320,9 @@ pub struct CallbackId(usize);
319320
/// parameters.
320321
static BUFFER_CALLBACK: Mutex<Vec<(CallbackId, BufferCallback)>> = Mutex::new(Vec::new());
321322

323+
/// Used to identify when to clear buffers.
324+
static CALLBACK_FLAG: AtomicU32 = AtomicU32::new(0);
325+
322326
/// Indicates that ASIOOutputReady should be called
323327
static CALL_OUTPUT_READY: AtomicBool = AtomicBool::new(false);
324328

@@ -1047,9 +1051,13 @@ extern "C" fn buffer_switch_time_info(
10471051
// This lock is probably unavoidable, but locks in the audio stream are not great.
10481052
let mut bcs = BUFFER_CALLBACK.lock().unwrap();
10491053
let asio_time: &mut AsioTime = unsafe { &mut *(time as *mut AsioTime) };
1054+
// Alternates: 0, 1, 0, 1, ...
1055+
let callback_flag = CALLBACK_FLAG.fetch_xor(1, Ordering::Relaxed);
1056+
10501057
let callback_info = CallbackInfo {
10511058
buffer_index: double_buffer_index,
10521059
system_time: asio_time.time_info.system_time,
1060+
callback_flag,
10531061
};
10541062
for &mut (_, ref mut bc) in bcs.iter_mut() {
10551063
bc.run(&callback_info);

src/host/asio/device.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::SupportedStreamConfigRange;
1717
use crate::SupportedStreamConfigsError;
1818

1919
use std::hash::{Hash, Hasher};
20-
use std::sync::atomic::AtomicI32;
20+
use std::sync::atomic::AtomicU32;
2121
use std::sync::{Arc, Mutex};
2222

2323
/// A ASIO Device
@@ -30,7 +30,7 @@ pub struct Device {
3030
// A driver can only have one of each.
3131
// They need to be created at the same time.
3232
pub asio_streams: Arc<Mutex<sys::AsioStreams>>,
33-
pub current_buffer_index: Arc<AtomicI32>,
33+
pub current_callback_flag: Arc<AtomicU32>,
3434
}
3535

3636
/// All available devices.
@@ -215,7 +215,8 @@ impl Iterator for Devices {
215215
return Some(Device {
216216
driver,
217217
asio_streams,
218-
current_buffer_index: Arc::new(AtomicI32::new(-1)),
218+
// Initialize with sentinel value so it never matches global flag state (0 or 1).
219+
current_callback_flag: Arc::new(AtomicU32::new(u32::MAX)),
219220
});
220221
}
221222
Err(_) => continue,

src/host/asio/stream.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ impl Device {
299299
// Create buffers depending on data type.
300300
let len_bytes = cpal_num_samples * sample_format.sample_size();
301301
let mut interleaved = vec![0u8; len_bytes];
302-
let current_buffer_index = self.current_buffer_index.clone();
302+
let current_callback_flag = self.current_callback_flag.clone();
303303

304304
let stream_playing = Arc::new(AtomicBool::new(false));
305305
let playing = Arc::clone(&stream_playing);
@@ -321,13 +321,13 @@ impl Device {
321321

322322
// Silence the ASIO buffer that is about to be used.
323323
//
324-
// This checks if any other callbacks have already silenced the buffer associated with
325-
// the current `buffer_index`.
324+
// Check if any other callbacks have already silenced the buffer associated with
325+
// the current callback. The flag is updated once per buffer switch.
326326
let silence =
327-
current_buffer_index.load(Ordering::Acquire) != callback_info.buffer_index;
327+
current_callback_flag.load(Ordering::Acquire) != callback_info.callback_flag;
328328

329329
if silence {
330-
current_buffer_index.store(callback_info.buffer_index, Ordering::Release);
330+
current_callback_flag.store(callback_info.callback_flag, Ordering::Release);
331331
}
332332

333333
/// 1. Render the given callback to the given buffer of interleaved samples.

0 commit comments

Comments
 (0)