Up-/downsampling 'on the fly'

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

I would like to upsample x (int) times 'on the fly' before adding filter(s) and then downsample back to original fs right after filtering's done. How's this done?

Maybe not the best place to do this there but 1st I try do it inside the process() method (maybe calling original process() x times in a loop would be better way?). My prototype code looks like this:

Code: Select all

	float process(float sample, int x){
		float result = 0.0f;
		float in_sample = sample;
		// upsampling by x times using 		
		float k = 1 / x;
		
		for(i = 0; i < x + 1; i++){
			sample = (1 - (i * k)) * old_sample + (i * k) * in_sample; //linear intepolation
			result = a0 * sample + a[0] * x1 + a[1] * x2 + a[2] * y1 + a[3] * y2;
			x2 = x1;
			x1 = sample;

			y2 = y1;
			y1 = result;
		}
	
		old_sample = sample; 
		
		//Input for next stage
		return result;
	}
Is there something totally wrong in my thought and/or code (I'm still a noob with everything related to DSP/math).

Post

This is something I did with my own JUCE based framework, so don't be put off by weird data types or calls. The concept is still migratable to non JUCE environments.

Code: Select all

void Stuff (AudioSampleBuffer* source, AudioSampleBuffer* target, unsigned int ratio)
{
    /* examine the dimensions of the source buffer */
    const unsigned int numChannels = source->getNumChannels();
    const unsigned int numSamples = source->getNumSamples();
    
    /* create working copy in case source==target */
    AudioSampleBuffer temp=FRAMEWORK::Stream::Create::Empty(numChannels,numSamples*ratio);
    
    /* go through every channel */
    for(unsigned int channel=0;channel<numChannels;++channel)
    {
        /* getting access to the streams' float array with the samples for this channel */
        const float* sourceData=source->getReadPointer(channel);
        float* tempData=temp.getWritePointer(channel);
        
        /* for every sample in the source stream */
        for(int sample=0;sample<numSamples;++sample)
        {
            /* calculate an offset; this is used to determine where in the oversampled
                stream to put the original samples. will be 0 for first one, so all good.     */
            const int offset=sample*ratio;
            
            /* for every ratio step */
            for(int step=0;step<ratio;++step)
            {
                /* add one repetition of this sample to the new stream */
                tempData[offset+step]=sourceData[sample];
            }
        }
    }
    
    /* copy working copy to target stream */
    target->makeCopyOf(temp);
}
Immediately after doing this, you'll have to apply a VERY steep low pass filter at the SOURCE's Nyquist, so 1/2 * SOURCE sample rate. If the original sample rate is 44100, then put the LP at ~22000. When you're done processing, just reverse the above. VERY steep low pass filter, remove the excess samples.

My OCD would recommend *not* to stuff/up-sample the original stream/AudioSampleBuffer/float array, but to create a working copy that is already sized to the correct amount of samples, then you don't have to resize one stream constantly.

For 4x oversampling, create a stream/float array that's (original float array size * 4) samples long, use above process to copy over 1 sample from the original into 4 samples of the oversampled one, finally apply a hard filter.

Then you do your filtering.

At the end, you reverse above process by applying a VERY hard LP filter at Nyquist to the oversampled stream, then you loop through the original stream (for int sample=0;sample<original.size;++sample), use (int sample * oversampling factor) as an offset and just copy over original[sample]=oversampled[sample*offset].

That should do the trick.

Note that my snippet stuffs the original sample into the up-sampled stream multiple times. I tried doing this with 1 original sample and (oversampling factor - 1) zeroes, this is called zero stuffing, but somehow the signal I got out in the end was -3 dBfs quieter than what I sent in. Reusing the original sample gave me identical volume after just up- and then downsampling.

In the end, I just went back to using JUCE's internal functionality for resampling. I couldn't get any filter to work as steep as theirs without being hugely CPU inefficient.
Confucamus.

Post

Hi Juha_p. I would avoid linear interpolation unless you dont care about the quality of the results. I would recommend either high order FIR or IIR low pass filters.

Zero pad your signal to up to the desired sample rate then filter at the original nyquist.

Do your processing at the new samplerate

If the processing was linear simply discard samples to downsample

If processing was non-linear then filter again at original nyquist and then discard samples

Post

Instead of zero padding + filtering, why not try interpolating the data with some similar algorithm to the ones which can be found from here:
http://yehar.com/blog/wp-content/upload ... 8/deip.pdf

You're going to be battling between the quality/steepness of the LPF vs. the interplation algorithm's accuracy anyway. Both leave their own potential artefacts so it might pay off to try both approaches.

Granted, the PDF's algorithms assume the interpolated data is already oversampled by N. This makes them easier to interpolate without audible artefacts.

Post

this is called zero stuffing, but somehow the signal I got out in the end was -3 dBfs quieter than what I sent in.
Zero stuffing by n divides the average volume by n, so you need to multiply the values going in by n. E.g. 4x oversampling means one input + three zeroes, so multiply the input by 4 before you filter it.

Repeating the same value (a -> a a a a instead of a -> a 0 0 0 ) is not good. Zero-stuff and filter, all the cool kids are doing it.
I would avoid linear interpolation unless you dont care about the quality of the results. I would recommend either high order FIR or IIR low pass filters.
This, all this. I mean, you can do interpolation, but use a decent algorithm. I like the 6-pt 3-o Cubic Hermite method found in Oli's book.

http://yehar.com/blog/wp-content/upload ... 8/deip.pdf

You can use IIR filters, but keep in mind that they ain't cheap, and I figure that you need at least 8th order filters to do it any justice.

Post

Here's a post about interpolating signals. I haven't tried the code myself so I can't comment on how well it works.

http://musicdsp.org/showArchiveComment.php?ArchiveID=62

Post

Rockatansky wrote:In the end, I just went back to using JUCE's internal functionality for resampling. I couldn't get any filter to work as steep as theirs without being hugely CPU inefficient.
Optimisation wise there are some options. For FIR we dont need to process the inserted zero samples for example, or it's also simple to do polyphase decomposition in the case of FIR filters. Combined with SSE the results are fast.

For IIR filters you could try using transposed form 2 biquad cascades as they perform better cpu wise than direct form 1 (they dont modulate well at all, but shouldnt be a problem for fixed ratio oversampling). Often with low pass filters the first and third feedforward coeffecients (of each biquad in the cascade) are of equal value. Normalise the coefficients so they (a0 and a2) equal 1. This saves two multiplies per biquad stage. The final output then just needs normalising with a single multiplication. I've had good results with this method.

Other than that for IIR low pass the filter type makes a big difference. You will get much sharper cutoffs at low orders with eliptical rather than say butterworth filters for example.

Post

If you use an interpolator like the yehar paper, for example, then you are using a polynomial approximation of a, rather short, sinc kernel. If you are doing integer fixed ratio resampling and are happy with a short kernel why not just use a short FIR? Then the coefficents are just read from a table rather than deriving them from a polynomial every sample.
Last edited by matt42 on Sun Jul 16, 2017 3:30 am, edited 1 time in total.

Post

Thanks for the replies! I'll check some of those suggested methods... .

Post

sault wrote:multiply the input by 4 before you filter it.
Interesting, hadn't thought of that. Will try, thanks for the hint!
sault wrote:Repeating the same value (a -> a a a a instead of a -> a 0 0 0 ) is not good. Zero-stuff and filter, all the cool kids are doing it.
Hm, it's not that I absolutely WANT to not believe you, but I'm learning about these things at the moment and I like to shape my own opinion by reading up on things. Do you have any links to articles or papers with actual reasons why zero stuffing is better than repeating the original sample? Surely it can't be a memory thing, and I doubt it makes calculating filters any more accurate or fast.
Also, it would seem to that zero stuffing brings the overhead of an additional multiplication for each sample going in that wouldn't be necessary if the volume was kept constant by re-using the same original sample?

Edit: never mind, the PDF linked by Kraku above explains it. :)
Confucamus.

Post

Here's a post about interpolating signals. I haven't tried the code myself so I can't comment on how well it works.

http://musicdsp.org/showArchiveComment.php?ArchiveID=62
I've already tested it. It is better than linear, almost as good as trilinear, but noticeably poorer than 6pt 3o cubic hermite.
If you use an interpolator like the yehar paper, for example, then you are using a polynomial approximation of a, rather short, sinc kernal. If you are doing integer fixed ratio resampling and are happy with a short kernal why not just use a short FIR?
Not really, although it depends on the interpolation method and oversampling ratio. A 31 tap 4x kernel will outperform a 7 tap 2x + interpolation, but it won't outperform a 19 tap 2x filter + cubic interpolation. So I guess it depends on what you mean by "short" and which interpolation method you're talking about. Linear is one thing, cubic hermite is another.

Post

Rockatansky wrote:
sault wrote:Repeating the same value (a -> a a a a instead of a -> a 0 0 0 ) is not good. Zero-stuff and filter, all the cool kids are doing it.
Hm, it's not that I absolutely WANT to not believe you, but I'm learning about these things at the moment and I like to shape my own opinion by reading up on things. Do you have any links to articles or papers with actual reasons why zero stuffing is better than repeating the original sample? Surely it can't be a memory thing, and I doubt it makes calculating filters any more accurate or fast.
Also, it would seem to that zero stuffing brings the overhead of an additional multiplication for each sample going in that wouldn't be necessary if the volume was kept constant by re-using the same original sample?

Edit: never mind, the PDF linked by Kraku above explains it. :)
Ya repeating samples acts like a lowpass filter on high frequencies below the source nyquist frequency which you may want to retain.

Possibly repeat-sample could be acceptable in applications which do not require flat high frequency response? Guitar or Bass processing, etc. Long ago I did it on a couple of FX and maybe the HF response wasn't flat but it did not sound completely terrible.

Maybe there are other possible faults of repeat-sample in addition to unintended HF filtering. Dunno.

Maybe extreme repeat-sample, 4X or greater, could exacerbate aliasing? High frequency test signals with large repeat-sample could begin to resemble naive non-antialiased pulse waves. Perhaps it would need as good or better brickwall filtering as would the equivalent amount of zero-stuffing?

Post

  1. Zero-order hold.
  2. I've had this argument in the past. In most cases FIR filters are a better solution; although cases do exist where they are not optimal. Ideally you'd select the optimal solution in every case. For something like a common 2x or 4x oversampling interpolator you simply can't beat a FIR unless you don't need significant filtering to begin with. Cases where you only need a very rough interpolation are quite esoteric but definitely do exist and so it isn't possible to say a FIR is always the best choice. If you don't know your problem well enough however you're probably safe to start out assuming a FIR is the best choice until you can prove it isn't optimal.
  3. IIR all-pass interpolators can be much more efficient than FIR filters where accessing the FIR kernel (memory) is more expensive than basic parallelized math. For example if you were to apply CUDA to the problem of real-time audio interpolation an IIR implementation could potentially work equally as well as you require with an order of magnitude less cost; for cases where you do not require a very steep or complex "many poles" filter.
  4. In cases where you do require an extreme filter a FIR implementation is probably the best solution if not involved in one; although direct convolution is no longer practical and things are then somewhat turned on their head.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

sault wrote:Not really, although it depends on the interpolation method and oversampling ratio. A 31 tap 4x kernel will outperform a 7 tap 2x + interpolation, but it won't outperform a 19 tap 2x filter + cubic interpolation. So I guess it depends on what you mean by "short" and which interpolation method you're talking about. Linear is one thing, cubic hermite is another.
I think you miss my point, but I guess I could have been clearer. Whatever kernel you use for your interpolation can be implemented as a FIR of the same length. The results will be the same (if you match the kernels), but the FIR will be cheaper as coefficients are pre calculated.

This is just for integer fixed ratio resampling of course. If we need to access the kernel at arbitary positions, say for a modulated chorus effect, then a fixed FIR is no longer of use and we need something like polynomial interpolation.

Post

Here are a few of my current thoughts about upsampling / downsampling :
  • The filtering part of the process can be done by either FIR filtering, IIR filtering or polynomial interpolators (the version which can be seen as a FIR filter with coefficients changing depending on the interpolation position, not the IIR one)
  • Usually, I go to FIR filtering when latency isn't a problem (compressors, mastering effects) and that I need a high quality filter, and I go to IIR filtering when I need a as low as possible latency (guitar amps) and that I don't need too much quality on the high frequency content of the signal
  • 4x oversampling and more => I prefer the approach with several 2x oversampling stages in cascade to the one Nx oversampling stage, because every processing at each stage is as efficient as it can be, and because it is possible to use less violent filtering for every stage after the 2x, since most of the important filtering matters there and less in further stages
  • FIR filtering => half band filters with symmetry and every two sample but the middle one is a zero
  • IIR filtering => polyphase half band filters with allpass filters in two paths in cascade
  • Latency is never zero even for IIR filters, so it must be calculated and handled properly
  • About polynomial interpolators, I don't like the fact that they are not transparent at all in the passband, so I use them only for resampling purposes when the ratio isn't integer

Post Reply

Return to “DSP and Plugin Development”