Desktop Recorder

klaus

Expert
Licensed User
Longtime User
EDIT : - ToRealAmplitude is not a good name because of "Real." I thought I might call it ToAmplitudes, note the plural, but is that too close to the existing ToAmplitude method name? I will also add the complementary ToPhases method.

Perhaps:
ToAmplitudePhase
ToAmplitude
ToPhase

Best regads.
 

agraham

Expert
Licensed User
Longtime User
- send, in the data() array to the FFT object, only the real values- the imaginary 0 samples are generated inernally in the FFT dll
That would waste some time. After you DIM an array .NET fills it with zeros so on a new array it is only necessary to fill in the Real values.
- get back only the first half of the real and imaginary samples, only the useful ones
I've already suggested that in the post above but I need a better name or to rename some existing methods, any suggestions?
or eventually use a 'FFT algorithm specialized for real and/or symmetric data' with the same inputs and outputs.
I'll leave that to someone who is more confident with the maths than I am.
I will make some improvements.
I'll wait till you have done that
 

redbird

Member
Licensed User
Allow me to summarize what I understood from these detailled posts, related to my example:

Filling the array only with 4096 real bytes from my sound sample will work mathematically, giving 100% correct results, but could take more time. Is that correct to assume ?

I understand the .NET mechanism that fills an array at declaration time with zeros, and see the benefits. I also understand now how the fft library is able to handle an input with only real values anyway, as it seems to generate the zero (imag) values itself internally.

BTW, I tried both methods, and besides the probable speed difference, the results looked good every time.

In my real life app, I only look for frequencies between 33 and 166 Hertz, being 1000 to 5000 RPM "headspeed" for a 2 bladed R/C helicopter. Simply 33*60/2 and 166*60/2. And until now, in this small, lower part of the spektrum, i only encountered this 1 frequency, together with 1 harmonic with exactly half the frequency, but less than half in amplitude. So my code can easily make the difference between those two, leaving no room for error.
I visualised this by means of a graph indeed, to be able to control this.
The user is of course responsible of making a recording with no other sound sources present. All the other frequencies generated by a R/C heli are much higher, being the 2-3 times faster turning tail blades, or the even higher RPMs of the electric motor, and all harmonics I found until now.
 
Last edited:

klaus

Expert
Licensed User
Longtime User
Hi Andrew,

I'm coming back on my suggestion 1) transfering in the Fft.Transform function only the real data.

I had a look at your library with SharpDevelop and saw that the real and imag samples could relatively easy be treated separately.
- real data in the input, half the length than before
- so, n = data.length and no more n = data.length/2
- generate an internal array for the imag data samples
- everywhere you have data(i) or data(j) is the real input data array realdata(i)
- everywhere you have data(i+1) or data(j+1) is the internal imag data array imagdata(i)
- for the return copy the first half of the imag samples into the second half of the data array.

Just to illustrate
B4X:
[SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j [/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]+ [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=#00008b][SIZE=2][COLOR=#00008b]1[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i [/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]+ [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=#00008b][SIZE=2][COLOR=#00008b]1[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i [/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]+ [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=#00008b][SIZE=2][COLOR=#00008b]1[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE][/COLOR][/SIZE]
would become
B4X:
real[SIZE=2]data[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][COLOR=black]real[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=black]da[/COLOR]ta[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imagdata[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][COLOR=black]imag[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2][COLOR=black]d[/COLOR]ata[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]realdata[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imagdata[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE][/COLOR][/SIZE]
Could you please have a look at this.

If this is could be done, then we do no more need two methods for Amplitude and Phase.

With ToAmplitudePhase I was thinking of a single method for getting both arrays, but two seperate functions are OK for me.

Best regards.
 

agraham

Expert
Licensed User
Longtime User
Try this, it's probably buggy as I haven't played with it. To keep things simple and retain the full inverse transform capability I didn't generate imaginary internal values. Dimming a new array initialised to zeroes is just as cheap in Basic4pc code as in C#.

void Transform(double[] real, double[]imag)

void Inverse(double[] real, double[] imag)

double[] CopyArray(double[] array)

double[] ToAmplitude(double[] real, double[] imag, double scale)

double[] ToPhase(double[] real, double[] imag)

ToAmpAndPhase(double[] real, double[] imag, double scale) ' half and half

double[] ToReal(double[] amplitude, double[] phase, double scale)

double[] ToImaginary(double[] amplitude, double[] phase, double scale)


EDIT :- It doesn't work. I don't understand the indexing (k) in the bitreverse method so can't get it right for separate Real and Image arrays and theres a similar problem (b) in the actual transform. I'm going back to the original algorithm but will make it accept and return separate arrays.
 

Attachments

  • FFT1.2.zip
    4.5 KB · Views: 6
Last edited:

klaus

Expert
Licensed User
Longtime User
Hi Andrew,

I just cam back home, and looked at the library.
What version is FFTV1.2 ?
The one before your edit ?

I had a look at the FFT.cs file and found that in lines 65-68
B4X:
[SIZE=2]real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE][/COLOR][/SIZE]
should be
B4X:
[SIZE=2]real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]j[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] = [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] - [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2][COLOR=red]real[/COLOR][/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_real[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2]imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400][[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]i[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400]] += [/COLOR][/SIZE][/COLOR][/SIZE][SIZE=2]wd_imag[/SIZE][SIZE=2][COLOR=#006400][SIZE=2][COLOR=#006400];[/COLOR][/SIZE][/COLOR][/SIZE]

This evening I won't be at home.

Best regards.
 

redbird

Member
Licensed User
Don't want to interrupt any subject here, but I thought I could mention a strange thing with those desktop and device recorder.dlls.

When analyzing a recorded (stereo) wave-file, made on my HTC Touch HD, I noticed that after the header (44 bytes long) all the bytes are filled with the same value (128 decimal), until position 7759 in the file, counting from position zero.

This does not happen with the (mono) desktop recording dll. After the header, there are "normal" amplitude bytes right away, that's from position 44.

I don't know if this might be related to my device, or a problem with a library or so, but I thought I just mention this.

In my app, I simply ignore the first 7760 bytes before extracting samples, just to be sure.
 

agraham

Expert
Licensed User
Longtime User
I think that stretch of silence can only be down to the device.

I think I got the algorithm working with separate arrays so here it is. The source code is messy as there is a lot of commented out stuff I don't want to lose until Klaus is happy with the library. Methods are as below. Now I'll start the help and wait for Klaus to check the library and come up with a demo.

void Transform(double[] real, double[]imag)

void Inverse(double[] real, double[] imag)

double[] CopyArray(double[] array)

double[] ToAmplitude(double[] real, double[] imag, double scale)

double[] ToPhase(double[] real, double[] imag)

ToAmpAndPhase(double[] real, double[] imag, double scale) ' half and half

double[] ToReal(double[] amplitude, double[] phase, double scale)

double[] ToImaginary(double[] amplitude, double[] phase, double scale)
 

Attachments

  • FFT1.4.zip
    5.2 KB · Views: 8

klaus

Expert
Licensed User
Longtime User
Hi Andrew,

The library works fine !!!
I am still improving the test program. Will be ready in a few days.
Adding sine number and parameters user defined and sine modulation showing the sideband phenomenon.

If I could abuse, I still would be pleased having a function like this:
Fft.Transform1(DataReal(),FFTReal(),FFTImag())
Fft.Inverse1(FFTReal(),FFTImag(),DataReal())

Where DataReal remains the same, DataImag generated internaly in the dll and FFTReal and FFTImag having half the length than the DataReal array.
In the Inverse1 function FFTReal and FFTImag must be converted in arrays having the conjugated values added.

Reason: the library user has in his program only arrays with useful values.
Fft.Transform and Fft.Inverse should remain.

Best regards.
 

klaus

Expert
Licensed User
Longtime User
Hi redbird,​

A few questions about your project:
- the sampling frequency is 11025 samples /s
- does this mean that you take one sample out of 4 in the wave file ?
- what accuracy of the blade speed do you expect ?
- what update frequecy would you accept ?​

With:
- a frequency of 11025 samples /s
- 4096 samples
you get:
- an accuracy of the speed of +/- 80 rpm
- wave file length need 0.37 s​

You could with:
- a sampling frequency of 5512.5 samples /s
- 4096 samples
get:
- an accuracy of the speed of +/- 40 rpm
- wave file length need 0.74 s​

Attached an Excel file with different configurations.​

Best regards.​
 

Attachments

  • Accuracy.zip
    22 bytes · Views: 6

redbird

Member
Licensed User
Hi Klaus,

- Because of the way the B4PPC recorder dlls are made now, I seem to be stuck to 11025 samples/second only.
- I take 4096 subsequent bytes out of a (mono) WAVE file, double for stereo of course, and reject 1 byte every 2 bytes to get a mono file.
- Your accuracy figures are correct, I came to the same conclusion, studying the FFTs I made with Excel. In fact, there is about 80 rpm between 2 frequencies, meaning any final result that is theoretically somewhere in between this 80 RPM interval, would have "jumped" to the most close-by value, so I was thinking the deviation would be +/- 40 RPM at most ? And this is close enough for my application. Barely, but still OK.
- The update frequency is of no real importance, as it is no real-time tachometer. This is not of much use with RC helis. And it makes things easier for me. The user has to make the short recording in the app, and only after that, the file is analyzed by my app. But the total time of this should stay within a few seconds, to keep it user-friendly. A new reading needs a new recording, initiated by the user.
- the wave file length I need now is indeed 0.372 seconds, but I use a longer sample, to avoid the "dead and silent" period (7760 bytes) that I see in
the beginning of the file when recording on my PDA.

I will try soon with a sample of 16384 bytes (2^14), being a 4 times longer sample of course. I estimated the accuracy to improve equally by a factor 4,
resulting in a 20 RPM interval, or +/- 10 RPM if my above way of thinking would be true.

I fail to understand your words about taking one sample out of 4, there might be something I don't get there ?


Best regards,

Raf

This draft of a simple user interface gives you the idea:

attachment.php
 

Attachments

  • Knipsel1.jpg
    Knipsel1.jpg
    14.8 KB · Views: 45

agraham

Expert
Licensed User
Longtime User
@Klaus - I've added the two transform methods you asked for and a third method, CopyData, that might be useful. There is now a more or less final help file in the archive.

I am not sure I have done the correct thing when reconstructing the complex conjugate parts. I notice, assuming a 512 sample FFT, that index 0 is the DC component and is not reflected in the complex part, so index 1 is reflected into index 511, 2 into 510, and so on. This leaves no value to be relected into index 256 so I have left it as zero.

I also note that when I add a DC component of value 1 to one of the Sine series, after Transform I get a DC amplitude of 2 whereas I expected 1. It seems to inverse transform back OK. Is there a simple explanation why it is twice what I thought it would be. :confused:
 

Attachments

  • FFT1.5.zip
    14.8 KB · Views: 7

agraham

Expert
Licensed User
Longtime User
Because of the way the B4PPC recorder dlls are made now, I seem to be stuck to 11025 samples/second only.
On the device go to Start -> Settings -> Input -> Options (Tab NOT button). The voice recording format selected there may alter what the library records.

EDIR:- Probably not. It looks like the format is burnt into the library code. :(
 
Last edited:

redbird

Member
Licensed User
EDIR:- Probably not. It looks like the format is burnt into the library code. :(

Good tip Andrew, never even tried. But I was also afraid that this samplerate is imposed by the dll. Not such a big deal right now, I got it working with 16384 bytes, as stated above. Only thing is it takes me about 10-12 seconds to read all that data from the wave file into an array on a PDA. Still acceptable though. On the desktop, you even can't notice there is a delay.
 

klaus

Expert
Licensed User
Longtime User
Hi Raf,
B4X:
I fail to understand your words about taking one sample out of 4,
there might be something I don't get there
I had in mind with the digital audio sampling rating of 44100 Hz and your 11025 Hz sampling rate you would need to take 1 sample out of 4.
But it seems that I'm wrong, is the sampling rate on the PPC's only 11025 Hz ?

B4X:
In fact, there is about 80 rpm between 2 frequencies
I don't agree with that:
- with a sampling rate of 11025 Hz you get 9.07E-5 s sampling time interval
- with 4096 samples this gives .3715 s time window duration
- this gives 1/.3715 = 2.69 Hz frequency interval which is 161 rpm so +/- 80 rpm

Best regards.
 

redbird

Member
Licensed User
I don't agree with that:
- with a sampling rate of 11025 Hz you get 9.07E-5 s sampling time interval
- with 4096 samples this gives .3715 s time window duration
- this gives 1/.3715 = 2.69 Hz frequency interval which is 161 rpm so +/- 80 rpm

Klaus, completely correct until the 2.69 Hertz, but then I guess there is something I did not mention clearly enough, my bad:

My app records the sound of a RC heli which typically has TWO blades, meaning that every sinlge rotation of the head produces two consecutive sounds. So, I am not recording shaft RPM, but blade sound, which is exactly 2 times faster. This made me state the number of 80 RPM accuracy (+/-40 RPM), it is as if the 2 blades double my sample rate for free :). I hope this is correct, but if I would make any error in thinking, I would gladly accept your opinion. My apologies for not mentioning this clear enough, it is a bit too obvious for me as a R/C hobbyist, I guess.
Of course, I need to take the 2 blades also into account when going from frequency to RPM.
In my case: RPM = 11025/4096*60/2*index_of_array().
 
Top