Any requirement for zero stuffing is caused by the code that follows the stuffing. If you wrote the filter functions to require pre-stuffing, that's on you and it leads to twice as much work as required being done in the first filtering stage. That's a significant hit.
Hardly. Modern compiler optimizations are well defined and your change does nothing for efficiency or readability.
Well then you'd better do all of the optimizing yourself. Now reorder all of your code to ensure the CPU execution pipelines stay full. Any pipeline stalls are on you.
If that's the case, VB6 is a toy language and you're boned until you switch to something sane. But you've already figured that out.
BTW, still curious why you're converting I/Q to real. Any clues?
Unless you are prepared to do some serious FFT work, then you absolutely need real values. All my processing is in the time domain, which (unless you plan to convert to the frequency domain) is a domain easiest to work with if it's purely real. I can easily write up some IIR or FIR (convolution) filters that work on time domain signals, but I've never had any success coding my own FFT. I've done some pure DFT stuff (not speed optimized, so it would be incorrect to to call it FFT), but that's just too slow for real-time signal processing. So I'm sticking to purely time-domain signal processing. For example, there's no such thing as a complex-value-time-domain lowpass filter, but there is a complex-value-frequency-domain lowpass filter, as well as a real-value-time-domain lowpass filter. So in order to perform any kernel/convolution/FIR filtering, or even any IIR filtering, on a time domain signal, it absolutely MUST be using real-value-time-domain. The RTL-SDR units only output complex-value-time-domain signals unfortunately. This means that my complex-to-real conversion is MANDATORY. I can do no further processing without this conversion being done first.
By the way, I can't even begin to figure out how to write the code to calculate an a true FFT (as opposed to the very easy to code DFT). I've looked at some of the online explanations of the math behind it, and I feel I'm taking a PHD-level physics course on quantum mechanics. So I just leave the website, feeling even more confused than before I visited it, and have vowed to NEVER waste may time learning how to write the code for an FFT. I'll leave that to the true signal processing experts/professionals.
As for VB6 being a toy language, it isn't really. Yes, it uses safearrays, but that simply allows it to check things like number and size of dimensions and other stuff when you use them. Without the arrays being represented by safearray objects, there is no way for VB6 to pop up an error box when you have attempted to access a cell to the array that's out of bounds. Safearrays aren't strictly for VB6 either. They are a Micorsoft thing, and the Windows APIs that use these objects can be called from C++. When used with the Windows API functions that operate on the safe arrays, they can be useful for writing code that checks to make sure you always read and write to the array within bounds so that errors can be given when you go out of bounds. That's as opposed to standard C and C++ arrays which are not actual objects, but just allocated memory regions, so are not safe, and so have no functions based around them that can be used to check if you are out of bounds. C and C++ arrays are dangerous and allow you to write all over your program's memory space if improperly used, causing hard crashes, or potentially allowing buffer overflows and other crap that might even allow a hacker to run carefully crafted code that exploits it (potentially allowing a hacker to cause your program to execute malware that further damages your computer or steals personal information).
The way that C and C++ does things may be faster, but it's easier for bugs to go completely unnoticed, except that every once in a while the program will do a hard crash without warning. Then debugging it can be quite difficult.
I use VB6 because of its "ease of use". Also it allows you to create a GUI based program. C and C++ are best used to write console applications, that have no visible window of their own displayed to the user (so no nice buttons or menus)
The only times I use MS Visual C++ are to compile a DLL file that can perform certain tasks at a greater speed than VB6 can, and then I call the DLL function(s) from within VB6 at the points in the code where this speed boost is needed. For everything else (making a program with a GUI, and writing 99% of the program's code) I use VB6.
By the way, I finally found out where the speed was REALLY taking a hit. It turns out there were 2 places. One was the way I was using classes, where each filter was an instance of a class. VB6's class function calling seems to be HUGELY slow. So instead I replace the classes with calls to standard functions, and just stored the internal state of instance of the function in a structure (known in C++ as a struct, or in VB6 as a User Defined Type).
Below is module's code that contains all of the functions and User Defined Types.
Code:
Private Const K1 As Double = -0.1061
Private Const K3 As Double = 0.31831
Private Const K4 As Double = 0.5
Private Const K5 As Double = K3
Private Const K7 As Double = K1
Public Type LPF_State
Input1 As Double
Input2 As Double
Input3 As Double
Input4 As Double
Input5 As Double
Input6 As Double
Input7 As Double
End Type
Public Type DCB_State
LastInput As Double
LastOutput As Double
End Type
Public Function LPF(ByRef State As LPF_State, ByVal Value As Double) As Double
With State
.Input1 = .Input2
.Input2 = .Input3
.Input3 = .Input4
.Input4 = .Input5
.Input5 = .Input6
.Input6 = .Input7
.Input7 = Value
LPF = .Input1 * K1 + .Input3 * K3 + .Input4 * K4 + .Input5 * K5 + .Input7 * K7
End With
End Function
Public Function DCBlock(ByRef State As DCB_State, ByVal Value As Double) As Double
With State
DCBlock = Value - .LastInput + .LastOutput * 0.9999
.LastInput = Value
.LastOutput = DCBlock
End With
End Function
I also optimized the use of Cos and Sin, since calling trig functions was another major hit to speed. Since there are only 4 possible values for a cosine wave and a sinewave, at half-bandwidth frequency and no phase-shift (the 4 values being 1,0,-1,0 for cosine, and 0,1,0,-1 for sine), I only need to create a 4 element array for Cosine and a 4 element array for Sine. And instead of the inputs being an angle, now the input is simply an integer index into the array.
Below is my new signal processing function with these optimizations.
Code:
Private Sub Process(ByRef WaveIn() As Byte, ByRef WaveOut() As Integer)
Dim n As Long
Dim i As Long
For n = 0 To &HFFFF&
Wave(0, n) = (WaveIn(n * 2) - 128) / 128
Wave(1, n) = (WaveIn(n * 2 + 1) - 128) / 128
Next n
For n = 0 To &HFFFF&
WaveResampled(0, n * 2) = Wave(0, n)
WaveResampled(1, n * 2) = Wave(1, n)
Next n
For n = 0 To &H1FFFF
WaveFiltered(0, n) = DCBlock(DCB_I, LPF(LPF2_I, LPF(LPF_I, WaveResampled(0, n))))
WaveFiltered(1, n) = DCBlock(DCB_Q, LPF(LPF2_Q, LPF(LPF_Q, WaveResampled(1, n))))
Next n
For n = 0 To &H1FFFF
i = n And 3
WaveOut(n) = (WaveFiltered(0, n) * Cosine(i) + WaveFiltered(1, n) * Sine(i)) * &H7FFF
Next n
End Sub
DCB_I and DCB_Q are variables of type DCB_State (one of the above mentioned user defined types)
LPF_I, LPF_Q, LPF2_I, and LPF2_Q are variables of type DCB_State (the other one of the above mentioned user defined types)
I've not stopped using floating point values in favor of integer values, and because this is done in VB6 I've not been able to switch to using faster unsafe arrays. However, the optimizations I have done are enough to keep the program from having an ever increasing lag. Now it runs at least as fast as the signal speed of 2.4 MSPS.
In the past, because the lag would increase the longer the program ran, the result was that after about a minute of processing, you'd have maybe processed only about 10 seconds of signal. This is no longer the case, which is REALLY GOOD. It now runs fast enough to keep up with the signal (not sure by how much though). I've got a couple ideas for further improving speed though, but none of them involve using integer values. There's a good reason for this. Integers don't support numbers that contain a fractional part (like 0.5 or 1.8), and values that are not integers routinely occur when performing filtering, which contains a division or multiplying by a non-integer number (check out the kernel values in the FIR filter and you will see that they all have an absolute value less than 1). Loss of the fractional part hurts the filter's functionality, and leaves artifacts in the processed signal.