rodio icon indicating copy to clipboard operation
rodio copied to clipboard

Fix audio distortion bug with SineWave source

Open mlindner opened this issue 6 years ago • 6 comments

Fixes #207

This fixes a audio distortion bug caused by float rounding errors and by the sinewave frequency not matching the frequency of the driver.

mlindner avatar Nov 07 '18 10:11 mlindner

the sinewave frequency not matching the frequency of the driver.

Well, that shouldn't be a problem. If the sine wave doesn't match the expected frequency, it should get converted automatically.

tomaka avatar Nov 10 '18 13:11 tomaka

I agree with using cur_val and iterating this way, but the change to the frequency is probably a different problem.

tomaka avatar Nov 10 '18 13:11 tomaka

I can only assume that the algorithm being used to subsample 48khz to 44.1khz is incorrect. Any audible signal here would be below the Nyquist frequency so there shouldn't be any distortion. Rodio always uses 44.1khz as the output format type on my system because it picks the default format given by cpal device which on my system is a 44.1khz output.

One of the things I want to fix in a future pull request is have rodio pick the format that best approximates the sampling rate of the input signal so resampling isn't required, or punt the format selection up to the application user. Do you have a preference?

mlindner avatar Nov 15 '18 12:11 mlindner

One of the things I want to fix in a future pull request is have rodio pick the format that best approximates the sampling rate of the input signal so resampling isn't required, or punt the format selection up to the application user. Do you have a preference?

I'm a bit conflicted, because originally I wanted cpal and rodio to be able to handle hardware that doesn't come with any way to resample. Which is why it selects the frequency based on the output device, and not the input. The other problem is that all inputs are mixed together, so if you have multiple different inputs of multiple frequencies, you have to convert at least one of them.

Ideally I'd say that there should be two modes of action: either we interface with a software engine (eg. ALSA), in which case we can open one new backend voice per input and thus pass the data through with the original frequency, or we are interfacing with some hardware, in which case we should select the most appropriate frequency based on the hardware, and do all the conversions ourselves.

tomaka avatar Nov 16 '18 10:11 tomaka

Many games even let you set the output device type and the output sample rate. I think limiting it to simply picking it automatically isn't helpful. It's trying to be too helpful. We can have the default value be automatic but it should also be allowed to specify the format.

Thoughts:

  • By default I'd have rodio pick something automatically by looking at the sample rate of the output. (The default setting is the audio device's default format.) However it should be configurable if the user of rodio wishes to force a specific output format.
  • At the next level up rodio should inspect the format that was selected either automatically or manually and insert resample filters for all incoming signals to that sample rate, but do no re-sample filtering for any samples that already have the correct sample rate.

This basically establishes two API layers. Later on down the line we should be able to transparently swap the "rodio picks the default format of the audio device" to "rodio passes raw samples to the lower level hardware layer and lets the hardware do the mixing".

Another thought, what I would suggest need to happen would have the lower level drivers like ALSA supply a list of traits that the driver implements up to cpal and cpal can then propagate those traits up to rodio and other applications. The traits would specify whether they can resample, whether they can mix, how many channels they can support (if for example we need to combine channels in software), etc. That should not conflict though with the other implementation of rodio that I just discussed. It will optionally fall back on doing some things itself if the lower level hardware doesn't support those features.

mlindner avatar Nov 16 '18 10:11 mlindner

Is there a reason this change wasn't merged? I just found this project and started poking around and was about to submit a very similar pull request.

Two comments:

  • Instead of creating its own constant PI_2 it could use std::f32::consts::TAU .
  • No multiplication or division is needed within the loop. Instead the structure can maintain an increment value set at creation time: increment: TAU * sample_rate.recip() * freq. Then within the iterator: self.cur_val += self.increment.

That said, I agree that this change wouldn't fix the distortion issue. This is however a simpler, slightly more efficient implementation of a sine wave. It also makes it easier to change the frequency after it's started without pops and cracks (if such a feature is desired in the future).

jayanderson avatar Jul 04 '22 04:07 jayanderson