fix(wasapi): shift i24 to/from MSB - #1309
Conversation
c81d134 to
78e4848
Compare
|
Thanks, this is a much simpler and most welcome direction than #1305. I'm doubtful whether it's correct though. Were you able to test by ear? // Simulates what WAVEFORMATEXTENSIBLE does with wValidBitsPerSample < wBitsPerSample
fn left_justify(buffer: &mut [u8]) {
for c in buffer.chunks_exact_mut(4) {
let v = u32::from_ne_bytes(c.try_into().unwrap()) << 8;
c.copy_from_slice(&v.to_ne_bytes());
}
}
fn main() {
let samples: [i32; 2] = [-1, 5];
let mut device_buf: Vec<u8> = samples.iter().flat_map(|s| s.to_ne_bytes()).collect();
left_justify(&mut device_buf);
let mut captured = device_buf.clone();
captured.copy_within(1.., 0);
let got: Vec<i32> = captured
.chunks_exact(4)
.map(|c| i32::from_ne_bytes(c.try_into().unwrap()))
.collect();
println!("expected: {:?}", samples);
println!("got: {:?}", got);
}
// expected: [-1, 5]
// got: [16777215, 5]Would something like this work instead? // render
for c in buffer_slice.chunks_exact_mut(4) {
let v = i32::from_ne_bytes(c.try_into().unwrap()) << 8;
c.copy_from_slice(&v.to_ne_bytes());
}
// capture
for c in slice::from_raw_parts_mut(buffer, byte_count).chunks_exact_mut(4) {
let v = i32::from_ne_bytes(c.try_into().unwrap()) >> 8;
c.copy_from_slice(&v.to_ne_bytes());
}Question: the capture one still writes into the buffer |
Yes, and it worked fine
Sry, I don't understand this part. Can you rephrase please?
16777215 in an i24 overflows, and wraps around to -1. So your example is actually showing correct behaviour
This is a good point. I'll fix that |
Double-checking: did your by-ear test include capture/recording specifically, or mostly render? If I'm right at all, this would be a capture issue.
That'd be right if we went through
After |
It includes both. I use the feedback example for testing
Crap, you're right, I missed that. And I guess my test just happened to work because I only tested conversion between integer formats. I'll try to test with a float conversion next time
Looks like my understanding of |
|
Not strictly UB but wrong for sure. #1305 described well how it sounded. |
|
tested with this code: use std::sync::mpsc;
use std::thread;
use cpal::{I24, Sample, SampleFormat};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
type SampleTypeIn = I24;
type SampleTypeOut = i32;
fn main() {
let host = cpal::default_host();
let dev_in = host.default_input_device().unwrap();
let dev_out = host.default_output_device().unwrap();
let cfg_in = dev_in .supported_input_configs() .unwrap().find(|cfg| cfg.max_sample_rate() == 48000 && cfg.sample_format() == SampleFormat::I24).unwrap().with_max_sample_rate();
let cfg_out = dev_out.supported_output_configs().unwrap().find(|cfg| cfg.max_sample_rate() == 48000 && cfg.sample_format() == SampleFormat::I32).unwrap().with_max_sample_rate();
let (sender, receiver) = mpsc::channel();
let err_cb = |error| panic!("{error}");
let stream_in = dev_in.build_input_stream(
cfg_in.into(),
move |data: &[SampleTypeIn], _| {
let array =
<[SampleTypeIn; 960]>::try_from(data)
.unwrap()
.map(f32::from_sample);
sender.send(array).unwrap();
},
err_cb,
None
).unwrap();
let stream_out = dev_out.build_output_stream(
cfg_out.into(),
move |data: &mut [SampleTypeOut], _| {
if let Ok(array) = receiver.try_recv() {
data[..960].copy_from_slice(&array.map(SampleTypeOut::from_sample));
}
else {
data.fill(SampleTypeOut::EQUILIBRIUM);
}
},
err_cb,
None
).unwrap();
stream_in.start().unwrap();
stream_out.start().unwrap();
thread::park();
}I tried all sorts of sample format combinations, and it always worked fine |
|
🙏 Question though, not out of vanity but seeking to understand, why not my earlier snippet like: // render, in place
for c in buffer_slice.chunks_exact_mut(4) {
let v = i32::from_ne_bytes(c.try_into().unwrap()) << 8;
c.copy_from_slice(&v.to_ne_bytes());
}
// capture, into scratch_buffer
for (dst, src) in scratch_buffer.chunks_exact_mut(4).zip(
slice::from_raw_parts(buffer, byte_count).chunks_exact(4)
) {
let v = i32::from_ne_bytes(src.try_into().unwrap()) >> 8;
dst.copy_from_slice(&v.to_ne_bytes());
}If this works all the same, it's simpler and prevents Two smaller ones:
|
Because my brain was in tunnel view and refused to process your messages properly 😅 Feeling a bit clearer in the head now, your use of ne_bytes does seem more correct, as my solution assumes little endian implicitly. I've also learned about arithmetic vs logical shift, and now understand why this works correctly. However, calling
Valid. I changed the
Fixed. |
|
oh, there's a merge conflict, 1 sec |
b35aa5f to
50a9f58
Compare
|
Rebased. It was just the changelog |
50a9f58 to
0363180
Compare
Ah, I see now that this was based on |
|
Do you want me to make a second PR for master ? |
|
Sure, that'd be great, once we've got this one ticked off. |
(cherrypicked from commit 684e31d)
supersedes #1305