0
$\begingroup$

I am trying to interpolate a signal using a third party lib that performs Sinc Function Interpolation on a signal. It works well if I input all the samples at once but introduce phase distortion if I try to perform by block of 1024 samples.

Some context: I am trying to increase or reduce the playback speed of an audio loop. The audio loop is a stereo interleaved samples with a total of 352,800 samples (176,400 stero frames).

It works flawlessly when I input all the 352,800 samples at once, but this can't be performed real time. Instead, I try to feed it with chunks of 1024 samples as requested by the audio interface.

To do that, I first calculate how much I have to feed the interpolation function to get an interpolated result that fits this requirement:

// how much samples we need to prune in the original samples
let to_take = asked_size * playback_rate;

Then I pass theses samples to the interp function that spits out excaclty 'asked_size' samples, theses samples have been interpolated.

The interpolation function takes the following parametters:

  • samples: the samples buffer to be interpolated (1024*playback_rate)
  • sinc function (this is the sinc function as in the lib).
  • playback_rate
  • delay (which I set to 0.0)
  • wing (how many samples to looks from each sides for interpolation / convolution).

When the playback rate is 1, everything is smooth, but when I move this value, the global signal get distorded, because there is gaps between the first sample of the next buffer and the last sample of the previous buffer that I send to audio.

I suspect there is something to do with the phase, or something to do with the fact that the interp function miss some contexts on previous chunks of samples (blocks of 1024) for the outpout (concatenation of all the blocks of 1024) to be seemsless, and this results in nasty distorted audio.

How should I correct this behaviour ?

EDIT: Added implementation in Rust:

// block loop
pub fn next_block(&mut self, size: usize) -> Vec<Stereo<f32>> {

  // how much we need to prune
  let to_take = ((size as f64) * self.playback_rate) as usize;

  // to interleaved samples (original frame format is Vec<[f32, 2]>)
  let interleaved: Vec<f32> = self.take(to_take)
     .flat_map(|x| x.to_vec()).collect();

  // to complex DspVec data struct
  let mut complex = interleaved.to_complex_time_vec();

  // interpolation is HERE !
  complex.interpolatef(
    &mut self.interp_buffer,
    &self.sinc_function,
    (1.0 / self.playback_rate) as f32,
    0.0,
    16,
  );

  // re-frame the signal
  let chunked = complex.to_slice().chunks(2);
  let mut out = Vec::new();
  for chunk in chunked {
    let c = [chunk[0], chunk[1]];
    out.push(c);
  }

  // send full buffer
  return out;

}

$\endgroup$
  • 2
    $\begingroup$ As you surmised, you have a bug in your implementation. Unless you show it, it's going to be very difficult to give you help. $\endgroup$ – Jason R Dec 3 '18 at 12:47
  • $\begingroup$ it would also help telling us which playback rates you're planning to support; in essence, your sinc interpolator should simply be a resampler with a sinc-impulse-response anti-image/anti-aliasing core filter, and that implies convolution of your continuous signal. So, can't be done blockwise without carrying over the current filter state to the next block. $\endgroup$ – Marcus Müller Dec 3 '18 at 12:51
  • $\begingroup$ Okay I add my rust implementation in the question for clarification. $\endgroup$ – Nunja Dec 3 '18 at 13:02
  • $\begingroup$ Okay I realize that this is a bit useless as I cannot see the details of interpolatef implementation. Maybe this function reset the state of the filter as @MarcusMüller stated $\endgroup$ – Nunja Dec 3 '18 at 13:10
  • $\begingroup$ I am trying to use this lib: github.com/liebharc/basic_dsp $\endgroup$ – Nunja Dec 3 '18 at 13:11
0
$\begingroup$

Ah, that's interpolatef (it's in vector/src/vector_types/time_freq/interpolation.rs).

I'm not fluent in Rust, but I think it calculates the interpolation by:

  1. Taking in a function (a real impulse response), and generating sampled vectors of that, with fractional offsets.
  2. It uses that polyphase filterbank to do the arbitrary resampling

You could hence just "prepend" every chunk with conv_len samples, and then "throw away" the right amount of samples from the beginning of the result.

However, that might be hard: Since this function allows for arbitrary real resampling ratios, conv_length * interpolation_factor might not be an integer.

So, the easiest thing would probably to restrict yourself to rational resampling ratios, here, and always transport a number of samples $n \ge \text{conv_len}$ with $\mathbb N \ni n = \frac m{\text{conv_len}}$ so that you can always throw away an integer amount of samples.

$\endgroup$
  • $\begingroup$ Thx a lot for the feedback ! Unfortunately, I have have the constraint of an ever changing resampling ratio, so I will look this function more in detail or switch to soxr lib (which by the way have also rust bindings :) $\endgroup$ – Nunja Dec 3 '18 at 15:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.