A Collection of Useful C++ Classes for Signal Processing

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

Robin from www.rs-met.com wrote: this is certainly true. but not having to duplicate source code should not be underestimated. if i should name one single thing that i consider as the root of all evil in software development, it would be duplication of source code.
Wanted to quote this for +1 insightful.
i think every programmer has the goal to make things as easy as possible. to me, programming often is like working really hard in order to avoid work.
That actually tends to be harder work than just doing the original work. On the bright side, my personal experience suggests that it seems to make things easier to maintain in the long run (well, at least a bit).

Post

my experience is, if you could have done it right the first time, you would have only done it once - and the "right" way tends to be the oo way in the end, once you eventually drag yourself there by one remaining limb.

i wish that i could have started out knowing what i know now - i wouldn't have chopped off all four limbs in the process of learning - but it turns out you didn't need them anyway - you only need to use your head from the beginning.

for me the idea of rewriting my libraries from scratch at this point seems extremely easy because i've reduced their complexity to such a minimal level - but to get there i had to throw out all the "extra limbs" and sawing off your last limb isn't easy.

that in my opinion is what c++ is really about. it isn't added complexity, it's reduced complexity. same resulting code, less source.

if you ever have to refactor - kick yourself. apparently you did it wrong previously and the refactor is a fix. try to do things that way from the start in the future. a refactor should always match or minimize complexity while increasing functionality. if a refactor doesn't do this, it's going backwards.

Post

aciddose wrote:if you ever have to refactor - kick yourself. apparently you did it wrong previously and the refactor is a fix. try to do things that way from the start in the future.
Wow...easier said than done, especially when building concurrent systems! A refactor is the rule for me, not the exception! I can think of very little I designed right on the first pass.

Post

of course - but still, kick yourself. we need to kick harder each time we end up refactoring our own code, since it likely could have been written right the first time if only that small extra effort had been invested. "no, it'll never need to do that" usually leads in the future to a refactoring to implement "that which never would need to be done."

Post

I tend to code in two stages. In the first stage, when I don't really understand the problem I'm trying to solve, I'll just start building quickly in whatever seems the most obvious way. This way I get close to a working prototype. From there I'll either start cleaning up and refactoring or start over if I've obviously gone the wrong way. I can't fit enough details in my head to design anything complicated from scratch on the first try.

Post

kuniklo wrote:I tend to code in two stages. In the first stage, when I don't really understand the problem I'm trying to solve, I'll just start building quickly in whatever seems the most obvious way. This way I get close to a working prototype. From there I'll either start cleaning up and refactoring or start over if I've obviously gone the wrong way. I can't fit enough details in my head to design anything complicated from scratch on the first try.
This is exactly the workflow that I use. Especially when building concurrent system...unexpected stuff comes up, requiring changes to architecture.

Post

i try to design in as many extra elements as possible and make as many optimizations / simplifications as possible. normally you'd think you'll never use that code because you don't have any cases which justify it at this point - but eventually what happens every time is you get another case, and another until you really do need the system for managing all those cases.

i've never managed to write a piece of code or feature that i've never used - of course it's the opposite, i'll always forget one or two things from the start and still need to do minor refactors or extra implementation.

for example when i first wrote my current graphics lib, i used a stack based shader system. for example;

gfx.push(add)
gfx.push(transparent, 0.5)
gfx.push(multiply)
gfx.push(checker, 6, 9)

gfx.draw ...

gfx.popall

you'd never think you would need such a thing when you first write it - i sure didn't see it as anything but a novelty.

now it turns out all the operations i do such as the simple drawing of text require multiple shaders to be pushed onto the stack - and this results in the elimination of 90% of the code that would otherwise need to exist in the drawtext() functions.

Post

One thing I've noted with this library is that the bandpass/stop/shelf filters are symmetrical on a linear frequency scale, whereas traditionally in EQ design they should be symmetrical on a log scale. So for instance, if you choose a center frequency of 1000 Hz and a bandwidth of 1000 Hz the band edges will be at 500 and 1500 Hz whereas they should be at 618 Hz and 1618 Hz traditionally. The following transformation fixes this:

Code: Select all

params[0]=sample_rate;
params[1]=2; //Order...
N=log(sqr(1/(2*Q)+sqrt(1/sqr(2*Q)+1)))/log(2.);
f1=f0/sqrt(pow(2,N));
f2=f1*pow(2,N);
params[2]=(f1+f2)/2; //"fake" center frequency
params[VinnFilter->findParamId(Dsp::ParamID::idGain)]=dBgain;
params[VinnFilter->findParamId(Dsp::ParamID::idBandwidthHz)]=f2-f1;

Post

Mr. Franco -- what an amazing library. I am deeply impressed and humbled by your generosity in releasing this.

I have a few questions from the briefest of interactions with it:
- the param model

This is unintuitive to me, that you have a bare argument array (although I guess it is for performance reasons / with iterating interpolation), and the ParamId enum doesn't map to the Param object indices.

For example, params[idOrder] won't give me the right float for the order parameter for an arbitrary filter.

- accelerated processing

Intel's IPP library processes IIRs with arbitrary constants, order etc. Is there an easy way to interface with this that you know of?

Post

I've been very busy and am just getting back to this topic. Boy, my C++ comments have really thrown this thread off topic. I'll just say a couple of things and then get back to the DSP Filter Library.

Somebody posted an example of C vs C++ dealing with a BMP file. But it looks suspicious. I don't believe C++ knows about BMP files, and if it does, that's not quite a fair comparison. I have a book that lists two pages of code just to load a BMP file. That's what I mean about bloated complexity. Also, I keep hearing about C++ being efficient and such, and yet I can simply point at the orders of magnitude in size of code these days to do similar things previously. The latest version of Acrobat Reader expands to over 200 Megabytes, compared to 15 Meg earlier. The latest Acrobat Reader doesn't really do much more than earlier versions, so I can't comprehend the massive size increase. This is typical of all software and operating systems. When C++ hit the Mac community, applications suddenly got a lot more bloated, bug-ridden, and loaded slowly (instead of a single exe, they were made up of lots of modules). Just as an end-user, I haven't seen much good come from it. One ex-Microsoft employee said that if Microsoft wrote in efficient assembly code, they could fit Windows on 3 floppy disks. I told him I have Macs that can boot and run their OS from a SINGLE floppy.

And to point out the C++ non-straightforwardness in this Filter Library: I can't simply create and array or Calloc some memory. NO! Because that's simple and straight forward, and if I try that, then the f.process() function doesn't like that. So I have to use thevinn's odd code:

float* audioData[1];
audioData[0] = new float[numSamples];

WTF? Even though it appears to set up arrays in some way, I can't actually use them as such. Can't use my own arrays either. Took me several tries to create a pointer to that mess, and when that finally worked, I don't know why, because the syntax seems wrong.

Or take a look at this:

Dsp::SimpleFilter <Dsp::Elliptic::BandPass <8>, 1> f;

Redundant "Dsp"s, totally haphazard parameters: the 8 is in < > brackets, the 1 isn't but it's inside the other < > brackets, and the "f" is hanging off the outside. To me, something like this in C would look like:

Elliptic(Bandpass,8,1);

So excuse me for thinking C++ looks like a convoluted mess.

ANYWAY, I'll create another post right behind this one for my DSP Filter related Trials and Tribulations, so we can get back on topic.

Post

For anybody thinking about using some of this IIR filters included in thevinn's library, I've learned a lot in my use that you may need to know, and ultimately I won't be able to use them and will have to go with FIRs for the reasons enumerated below.

I needed a 24 bandpass filter bank for a pitch shifter I wrote that previously used FFTs for filtering, but I wanted IIR filters for low computation and low latency. I needed this DSP library to give me the coefficients. I wrote my biquads in SSE assembly code that processes two interleaved groups of 4 bandpass filters each for incredible speed. I can process 1 minute of audio through 24 bands of 8 stage Elliptic filters in just .373 seconds, or in other words, run 160 filter banks at once. Now for all of the problems, some of which show up in the filter plotting software I wrote where I can see the filter's response when filtering a logarithmically swept sine wave.

(1) The filters seemed relatively well behaved from about 200 Hz and up, but below that they start to widen up and lose it. Normally you add more stages to give steeper cutoffs, but doing so below 200 Hz caused them to widen up even more. What?

(2) On the lowest bands between 65 Hz and 200 Hz, there was a lot of noise showing in my plots and I could hear it when listening to the individual bands when the sound got louder. Here's what I found out. The low bands only come out clean when using Double Precision math. When using Single Precision in the FPU unit, there was some noise. When using Single Precision in the SSE registers, there was a lot of noise. The SSE registers always need to chop down results to single precision since that's all they can hold, unlike the FPU unit which loads singles into its 80 bit registers and works in higher precision until you write the result back out. Later I give a trick around the problem.

(3) At one point I ran a single 1 sample pulse through the filter bank to get an impulse response. The entire thing slowed down by a factor of 10. This brought up something I worried about in filters with feedback loops; the possibility of numbers dropping below the range of that what floating point can represent. Reaktor mentions this in their manual. What's add though, is that I can take a wav file of music, zero out a bunch of it, and oddly it doesn't exhibit the problem. So I'm confused. I don't know whether the biquads need some conditional checking or not to test for values going beyond a certain range or where that would even go.

(4) And the last main problem is the sound and behavior of the filters. The lower the frequency of the filter, the longer the lag time before it comes up full output. They are sluggish. Slow build up, slow drain causes them to become like little resonating chambers. The end result is that music loses its punch, sharp percussive sounds like drums sound weird, and the audio sounds like its coming from inside a small resonant booth.

Those are things to consider if you're looking to use Butterworth, Elliptic, Cheb, filters. One tip for getting around some of the problems with the low bands is to subsample the input waveform by 2 or 4 (will need anti-aliasing filter) which has the effect of upping the pitch by and octave, or two and running it through the higher bands, and then supersampling on the output to bring the pitch back down.

Below are a couple of images from my filter plot program written in FreeBASIC which make doing thins like this far faster and easier than C++! First image shows FPU Double verses Single precision. Second image shows only 6 of the 24 bands where you can see how the filters change shape and get very noisy (SSE single precision) in the low end.

http://i127.photobucket.com/albums/p122 ... Single.jpg

http://i127.photobucket.com/albums/p122 ... ilters.jpg

So there's some data for you to consider.

-Elhardt

Post

Elhardt wrote:I have to use thevinn's odd code:

float* audioData[1];
audioData[0] = new float[numSamples];

WTF? Even though it appears to set up arrays in some way, I can't actually use them as such. Can't use my own arrays either. Took me several tries to create a pointer to that mess, and when that finally worked, I don't know why, because the syntax seems wrong.
The interface is expecting an array of channels. I guess I could have provided convenience functions for when the data to be filtered is a mono channel but why didn't you just pass &audioData and use calloc() like you wanted?
Dsp::SimpleFilter <Dsp::Elliptic::BandPass <8>, 1> f;

Redundant "Dsp"s, totally haphazard parameters: the 8 is in < > brackets, the 1 isn't but it's inside the other < > brackets, and the "f" is hanging off the outside.
"Dsp" is not redundant. You have to qualify SimpleFilter and Elliptic. If you don't want to do that, then add a using statement.

As for the syntax...well that's how it is. BandPass <8> declares the kernel and the ,1 adds on single-channel state variables for processing.
To me, something like this in C would look like:

Elliptic(Bandpass,8,1);

So excuse me for thinking C++ looks like a convoluted mess.
C cannot even come close in terms of the features in DspFilters that C++ allows. It is only a convoluted mess because you don't understand it yet. You seem fairly smart though, if you apply yourself and quit being obstinate I think you'll "get it" in fairly short order.

Post

Elhardt wrote:the last main problem is the sound and behavior of the filters.
I think this is a problem with IIR filters in general, and perhaps because of the simplistic method I used in the design process. I doubt the problem is specific to DspFilters although if there's a reproducible defect I'd be more than happy to take a look at it.

IIR filters with low cutoffs definitely start getting wacky especially as the order increases. I think the typical solution is "oversampling".

Can anyone confirm or deny this?

Post

using namespace Dsp;
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

Elhardt wrote: (1) The filters seemed relatively well behaved from about 200 Hz and up, but below that they start to widen up and lose it. Normally you add more stages to give steeper cutoffs, but doing so below 200 Hz caused them to widen up even more. What?
Assuming the coeff calculations are done right (I haven't looked at what the lib does) depending on your samplerate and precision, Direct Form filters often have some precision problems at very low cutoffs. 200Hz sounds fairly high though. I assume these are empirical results (from actually running an algo) and not from plotting the analytical response?

I've been meaning (for a while actually) to write a simple tutorial on how to pick an arbitrary filter structure and (assuming the structure is general enough) map any stable biquad (and by extension higher order filters too) on the result. There are also well-known formulas for ladder/lattice combos and probably some more filters.

Anyway, the first thing I'd do is pick the coeffs (in the precision they are calculated) and feed them to some math software that knows (or can be teached) how to plot you the response. If the results still seem off, then either there is problems in the formulas, or they've been written in a way that suffers from rounding problems. If OTOH the results seem fine, and empirical results are significantly off, then you are suffering from numerical issues during processing, and might benefit from modified/alternate filter structure.
(2) On the lowest bands between 65 Hz and 200 Hz, there was a lot of noise showing in my plots and I could hear it when listening to the individual bands when the sound got louder. Here's what I found out. The low bands only come out clean when using Double Precision math. When using Single Precision in the FPU unit, there was some noise.
Ok this sounds like you have a problem with numerical precision. Once you hit precision issues, the response can deviate pretty far from the desired as well. The first thing I'd do is try ladder/lattice combo (those are easy to implement, easy to convert coeffs from direct form, and might improve things a bit; there are other options though).
(3) At one point I ran a single 1 sample pulse through the filter bank to get an impulse response. The entire thing slowed down by a factor of 10. This brought up something I worried about in filters with feedback loops; the possibility of numbers dropping below the range of that what floating point can represent.
Yeah the dreaded denormal issue. Since it sounds like you're using SSE anyway, set the FTZ (and possibly DAZ) bits in SSE control word, and the hardware will just treat them as zero. Generally speaking on x87 (or if the bits are not set on SSE) you need to treat the denormals in each and every feedback loop, unless you inject some bias signal that keeps them from every happening.
(4) And the last main problem is the sound and behavior of the filters. The lower the frequency of the filter, the longer the lag time before it comes up full output. They are sluggish. Slow build up, slow drain causes them to become like little resonating chambers.
This is normal, and there isn't really anything you can do about it, other than lower the Q (or made the transition band wider in case of FIR filters). It's the age old problem of having to trade frequency-domain selectivity for time-domain accuracy. The only potential exception is some non-linear filters that might be able to dodge this issue if the non-linearities lead to temporarily lowered Q during transients.. but you will get plenty of distortion (and quite a bit of CPU overhead) if you try to do this.

Post Reply

Return to “DSP and Plugin Development”