diwic / alsa-rs

Thin but safe ALSA wrappers for Rust
Apache License 2.0
139 stars 66 forks source link

io_i32_s24() wavefolding issue #126

Closed JulienH2000 closed 4 weeks ago

JulienH2000 commented 1 month ago

Hello, I've been fighting for a bit against some strange behavior of my DSP. My audio pipeline is working fine from end to end, but in certain condition, the audio is awful. Going deeper, I noticed these : "output = input" -> Perfect Audio "output = 1.0001 input" -> Perfect Audio "output = 0.98 input" -> Massive distortion "output = 0.7 * input" -> Extreme distortion, original audio unrecognizable

After a lot of tests and audio sample data analyze, I think I'm onto something : the io_i32_s24 method, for some reason, doesn't support the negative value of the signal, so it produces a signal where all the negative parts of the signal are flipped and mapped at the max value of the 32 bits integer. Thus, it produced a signal highly incompatible with any linear signal processing, even a simple multiplication outbound this top negative part, and wrecks everything... Clearly both read and write use this method, so input straight to output is working perfectly fine.

The i16 handles fine the negative values in both read & write. (The synth example of the repo is written with i16 for example, but it surely won't work with i32_s24).

I'm attaching some plotting of the data I have gathered, I can provide more if needed.

Maybe I'm clueless and this is a normal behavior, but to me this crate is unusable with 24 bits PCM audio..... Don't hesitate, if you needed any more data or tests. Thanks a lot Sine 440 i32 vs i16 detailed i32 i32 In vs In * coef i32 Sine 440

diwic commented 4 weeks ago

The first plot shows input in s24 format with a zero byte in between. I don't have a better solution for this than to manually process input samples with the formula if s >= 2^23 { s - 2^24 } else { s }.

Outputs is likely unaffected by this problem, the fourth byte will be ignored so it does not matter. Is that consistent with your findings?

JulienH2000 commented 4 weeks ago

Bullseye ! It perfectly makes sense ! I just completely forgot that sample are coded in two's complement. So i'm processing each sample bitwise like this : pub fn s24_to_i32 (s: &i32) -> i32 { if (s & (1 << 23 )) != 0 { return s | 0xFF << size_of::() 8 - 8 } else { return s } } Thanks a lot ! You've just solved days of troubleshooting ! Have a nice day