Search code examples
matlabaudiofile-iooctave

Audio distortion when block streaming from a file (Octave)



I am setting up a simple audio IO system which simulates 'real-time block processing' by calling a block at a time from a file that is already stored in memory.


At the minute, I have a simple script which retreives the data from a file, and then enters a while loop which extracts one block at a time and provides a first order butterworth lowpass filter at 600 Hz (a skeleton setup to test). Each block is then processed and added onto another array which is declared outside the scope of the while loop, so that the processed data can be written to a wave file after completion.


To filter the data I am using the Octave signal pkg to generate the coefficients (butter), and then the built in filter function to apply the IIR filter.

The problem is that if I apply no filter affect i.e. input = output the audio sounds the exact same. However, if I apply a filter every time a block is called, a ringing is created that digitally distorts the signal quite heavily.


Please see the following script for the setup (it is only handling mono audio for the minute).

# Reset
close all; clear all;

# Audio file path
fileName = 'test.wav';

# Init routines
[x,fs] = audioread(fileName);
xlen = length(x);
[dim1,dim2] = size(x);
y = zeros(dim1,dim2);
[b,a] = butter(1, (600./(fs*0.5)));    
index = 1;
blockSize = 256;

# Enter process loop
while(index + blockSize < xlen)

  # Extract one block
  audioBlock(:,1) = x(index : index + blockSize - 1, 1);

  # Do process
  outAudioBlock = filter(b,a,audioBlock);

  # Store output block
  y(index : index + blockSize - 1, 1) = outAudioBlock(:);

  # Update index 
  index += blockSize;

endwhile

# Write to outputs
audiowrite('processed.wav', y, fs);
audiowrite('processed1.wav', filter(b,a,y), fs);

The second audiowrite is just an example which confirms filtering the whole audio data in one call creates no distortion, wheareas the block filtering creates noticeable digital distortion.


As a side note :

I have also attempted using different filtering techniques with frequency domain multiplication with windowing and then ifft back (with octaves fftfilt & using just the fft) as well as time domain convolution and creating an overlap add method. The same effect also occurs when applying an FIR filter rather than using IIR coefficients.

I am also aware that this example disregards the last block or so of audio but for this use case I am not bothered with the last block's zero padding.


I am not sure what I am missing; any ideas?

EDIT 1: The idea was not to use frequency domain processing if possible (just the time domain IIR/FIR filtering), but I investigated the frequency domain multiplications to see if a similar distortion result occured (which it did).


Solution

  • This is most likely an edge effect thing. You apply a causal IIR filter to audioBlock. To compute the first sample, the state is initialized to all zeros. If I remember correctly, this is equivalent to assuming that the signal before the first sample is all zeros. This likely creates a discontinuity, which will affect a certain number of samples at the beginning of the block. Because you use an IIR filter, this effect could potentially carry on for a long time. Using a FIR filter is safer in that respect.

    Let's assume margin samples are affected. You could amend your code as follows to extend your signal by that amount and prevent the distortion:

    while(index + blockSize < xlen)
    
      % Extract one block
      if index==1
        audioBlock = x(index : index + blockSize - 1);
      else
        audioBlock = x(index - margin : index + blockSize - 1);
      end
    
      % Do process
      outAudioBlock = filter(b,a,audioBlock);
    
      % Store output block
      if index==1
        y(index : index + blockSize - 1) = outAudioBlock;
      else
        y(index : index + blockSize - 1) = outAudioBlock(margin+1:end);
      end
    
      % Update index 
      index += blockSize;
    
    end
    

    (Disclaimer: I don't have octave installed here, and my copy of MATLAB doesn't have the signal processing toolbox, so I cannot test the above code.)


    Unsolicited advice:

    • Your data is all 1D, use 1D (linear) indexing. It's more efficient, and shorter to type. (See my code above.)

    • Don't do audioBlock(:,1) = when extracting a new bit of signal. Simply assign the result to the variable. It's much faster, and won't give problems if the signal size changes and you forget to reset the variable.

    • Don't start with close all; clear all;. Instead, write function <filename> at the top of the script. This converts the script into a function, meaning it gets its own workspace. This is a much safer way of working, as you don't accidentally use existing variables in your script, and you don't accidentally erase anything in your base workspace.

    • I used end instead of endwhile. This is the same, but also works on MATLAB. There is no reason not to use the most portable option.

    • I used % instead of #. Again, the same but portable. Note how the SO syntax highlighting works with % but not #! :)