CHackbart 13 Posted November 29, 2021 Hi, I currently try to write a fft visualizer (without any third party), but the last time I heard from dft/fft is about 20 years ago. This is what I did from scratch, but the implementation does not seem to be correct. Maybe you have an idea? unit umain; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, bgrabitmap, BGRABitmapTypes, uacinerella; type TSpectrum = (tspWaveform, tspFFT, tspLogarithmic, tsp3d); TComplex = record re, im: double; end; TComplexArray = array of TComplex; TDoubleArray = array of double; { TForm1 } TForm1 = class(TForm) Image1: TImage; procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private FInstance: PAc_instance; FInput: TFileStream; FAudiodecoder: PAc_decoder; FBitmap: TBGRABitmap; FSpectrumPalette: array [0 .. 255] of TBGRAPixel; FSpectrum: TSpectrum; FSpectrumPos: integer; FHanningWindow: TComplexArray; function ReadProc(Buf: PByte; Size: integer): integer; procedure ProcessAudio(Buf: PByte; Size: integer); procedure ProcessData(); procedure CreatePalette; public end; var Form1: TForm1; implementation uses Math; {$R *.lfm} function read_proc(Sender: Pointer; buf: PByte; size: integer): integer; cdecl; begin Result := TForm1(Sender).ReadProc(buf, size); end; function cInit(const re, im: double): TComplex; begin Result.re := re; Result.im := im; end; function cadd(const a, b: TComplex): TComplex; begin Result.re := a.re + b.re; Result.im := a.im + b.im; end; function csub(const a, b: TComplex): TComplex; begin Result.re := a.re - b.re; Result.im := a.im - b.im; end; function cmul(const a, b: TComplex): TComplex; begin Result.re := a.re * b.re - a.im * b.im; Result.im := b.re * a.im + a.re * b.im; end; function cabs(const a: TComplex): double; begin Result := sqrt(a.re * a.re + a.im * a.im); end; function getBit(const Val: DWord; const BitVal: byte): boolean; begin Result := (Val and (1 shl BitVal)) <> 0; end; function enableBit(const Val: DWord; const BitVal: byte; const SetOn: boolean): DWord; begin Result := (Val or Int64(1 shl BitVal)) xor (integer(not SetOn) shl BitVal); end; function mirrorTransform(n, m: integer): integer; var i: integer; begin Result := 0; for i := 0 to m - 1 do Result := enableBit(Result, m - 1 - i, getBit(n, i)); end; function doPermutation(Source: TComplexArray; m: integer): TComplexArray; var i, n: integer; begin n := length(Source); SetLength(Result, n); for i := 0 to n - 1 do begin Result[i] := Source[mirrorTransform(i, m)]; end; end; function doStep(k, M: longint; prev: TComplexArray): TComplexArray; var expTerm, substractTerm: TComplex; q, j, offset: longint; begin offset := system.round(intpower(2, M - k)); SetLength(Result, length(prev)); for q := 0 to system.round(intpower(2, k - 1) - 1) do begin // For each block of the matrix for j := 0 to (offset - 1) do begin // Fo each line of this block // First half Result[q * 2 * offset + j] := cadd(prev[q * 2 * offset + j], prev[q * 2 * offset + j + offset]); // Second half expTerm.re := cos((j * PI) / offset); expTerm.im := sin((j * PI) / offset); substractTerm := csub(prev[q * 2 * offset + j], prev[q * 2 * offset + j + offset]); Result[q * 2 * offset + j + offset] := cmul(expTerm, substractTerm); end; end; end; function doFFT(g: TComplexArray; order: integer): TComplexArray; var previousRank, nextRank: TComplexArray; i: integer; begin previousRank := g; for i := 1 to order do begin nextRank := doStep(i, order, previousRank); previousRank := nextRank; end; Result := doPermutation(nextRank, order); end; function WindowHann(const ACount: integer): TComplexArray; var i: integer; begin SetLength(Result, ACount); for i := 0 to ACount - 1 do Result[i] := cInit(0.5 * (1.0 - cos((2.0 * PI * i) / ACount)), 0); end; function CalculateSpectrum(const ASignalWindow, AData: TComplexArray): TDoubleArray; var dftResult, tempData: TComplexArray; len: integer; i: integer; begin len := length(ASignalWindow); SetLength(tempData, len); for i := 0 to Len - 1 do begin if i < len then tempData[i] := cmul(AData[i], ASignalWindow[i]) else tempData[i] := cInit(0, 0); end; dftResult := doFFT(tempData, 10); Setlength(result, Length(dftResult)); for i := 0 to length(result) - 1 do result[i] := cabs(dftResult[i]); end; { TForm1 } procedure TForm1.FormCreate(Sender: TObject); var i: integer; info: TAc_stream_info; begin FAudiodecoder := nil; FInput := TFileStream.Create('Tones_100_20000_incrementing.ac4.trp', fmOpenRead); FInstance := ac_init(); ac_open(FInstance, self, nil, @read_proc, nil, nil, nil); for i := 0 to FInstance^.stream_count - 1 do begin ac_get_stream_info(FInstance, i, @info); if info.stream_type = AC_STREAM_TYPE_AUDIO then begin FAudiodecoder := ac_create_decoder(FInstance, i); break; end; end; FBitmap := TBGRABitmap.Create; FBitmap.SetSize(640, 480); CreatePalette; FSpectrum := TSpectrum.tspLogarithmic; FHanningWindow := WindowHann(1024); end; procedure TForm1.FormActivate(Sender: TObject); begin while not application.terminated do begin ProcessData(); application.ProcessMessages; end; end; procedure TForm1.FormDestroy(Sender: TObject); begin ac_free_decoder(FAudiodecoder); ac_close(FInstance); FInput.Free; FBitmap.Free; end; procedure TForm1.CreatePalette; var i: integer; begin for i := 1 to 127 do with FSpectrumPalette[i] do begin Alpha := 255; Blue := 0; Green := 256 - 2 * i; Red := 2 * i; end; for i := 0 to 31 do begin with FSpectrumPalette[128 + i] do begin Alpha := 255; Red := 0; Green := 0; Blue := 8 * i; end; with FSpectrumPalette[128 + 32 + i] do begin Alpha := 255; Red := 8 * i; Green := 0; Blue := 0; end; with FSpectrumPalette[128 + 64 + i] do begin Alpha := 255; Red := 255; Green := 8 * i; Blue := 8 * (31 - i); end; with FSpectrumPalette[128 + 96 + i] do begin Alpha := 255; Red := 255; Green := 255; Blue := 8 * i; end; end; end; function TForm1.ReadProc(Buf: PByte; Size: integer): integer; begin Result := FInput.Read(Buf^, Size); end; procedure TForm1.ProcessData; var pack: PAc_package; begin if not assigned(FAudioDecoder) then exit; pack := ac_read_package(FInstance); try if (pack = nil) or (pack^.stream_index <> FAudiodecoder^.stream_index) then exit; if (ac_decode_package(pack, FAudiodecoder) > 0) then ProcessAudio(FAudiodecoder^.buffer, FAudiodecoder^.buffer_size); finally ac_free_package(pack); end; end; procedure TForm1.ProcessAudio(Buf: PByte; Size: integer); const BANDS = 28; var X, Y, Y1, V, B0, B1, SC: integer; Sum: single; i, z, w: integer; Step: integer; ptr: PSmallInt; signal: TComplexArray; fft: array of double; function FixRange(const Y: integer): integer; begin Result := Y * 127 div FBitmap.Height; end; begin if Size = 0 then exit; if FSpectrum <> tsp3d then FBitmap.FillTransparent; Y := 0; X := 0; if FSpectrum <> tspWaveForm then begin ptr := PSmallInt(Buf); Step := (Size div 4) div Length(FHanningWindow); if Step < 1 then Step := 1; SetLength(signal, length(FHanningWindow)); for i := 0 to high(signal) do begin signal[i].re := (32768-ptr^)/65536; signal[i].im := 0; Inc(ptr, step); end; fft := CalculateSpectrum(FHanningWindow, signal); end; Step := (Size div 4) div FBitmap.Width; if Step < 1 then Step := 1; ptr := PSmallInt(Buf); case FSpectrum of tspWaveform: begin for x := 0 to FBitmap.Width - 1 do begin V := (32767 - ptr^) * FBitmap.Height div 65536; Inc(ptr, step); if X = 0 then Y := V; repeat if Y < V then Inc(Y) else if Y > V then Dec(Y); FBitmap.SetPixel(X, Y, FSpectrumPalette[FixRange(abs(Y - FBitmap.Height div 2) * 2 + 1)]); until Y = V; end; end; TSpectrum.tspFFT: // "normal" FFT begin Y1 := 0; for X := 0 to (FBitmap.Width div 2) - 1 do begin Y := Trunc(sqrt(fft[X + 1]) * 3 * FBitmap.Height - 4); if Y > FBitmap.Height then Y := FBitmap.Height; // cap it Y1 := (Y + Y1) div 2; if (X > 0) and (Y1 > 0) then // interpolate from previous to make the display smoother while (Y1 >= 0) do begin FBitmap.SetPixel(X * 2 - 1, FBitmap.Height - Y1 - 1, FSpectrumPalette[FixRange(Y1 + 1)]); Dec(Y1); end; Y1 := Y; while (Y >= 0) do begin FBitmap.SetPixel(X * 2, FBitmap.Height - Y - 1, FSpectrumPalette[FixRange(Y + 1)]); // draw level Dec(Y); end; end; end; TSpectrum.tspLogarithmic: // logarithmic, acumulate & average bins begin B0 := 0; for X := 0 to BANDS - 1 do begin Sum := 0; B1 := Trunc(Power(2, X * 10.0 / (BANDS - 1))); if B1 > 1023 then B1 := 1023; if B1 <= B0 then B1 := B0 + 1; // make sure it uses at least 1 FFT bin SC := 10 + B1 - B0; while B0 < B1 do begin Sum := Sum + fft[1 + B0]; Inc(B0); end; Y := Trunc((sqrt(Sum / log10(SC)) * 1.7 * FBitmap.Height) - 4); // scale it if Y > FBitmap.Height then Y := FBitmap.Height; // cap it while (Y >= 0) do begin w := Trunc(0.9 * (FBitmap.Width / BANDS)); for z := 0 to w - 1 do FBitmap.SetPixel(X * (FBitmap.Width div BANDS) + z, FBitmap.Height - Y - 1, FSpectrumPalette[FixRange(Y + 1)]); Dec(Y); end; end; end; TSpectrum.tsp3d: // "3D" begin for X := 0 to FBitmap.Height - 1 do begin Y := Trunc(sqrt(fft[X + 1]) * 3 * FBitmap.Height); // scale it (sqrt to make low values more visible) if Y > FBitmap.Height then Y := FBitmap.Height; // cap it if Y < 0 then Y := 0; if (FSpectrumPos < FBitmap.Width) and (X < FBitmap.Height) then FBitmap.SetPixel(FSpectrumPos, X, FSpectrumPalette[128 + FixRange(Y)]); // plot it end; // move marker onto next position FSpectrumPos := (FSpectrumPos + 1) mod FBitmap.Width; for X := 0 to FBitmap.Height - 1 do if (FSpectrumPos < FBitmap.Width) and (X < FBitmap.Height) then FBitmap.SetPixel(FSpectrumPos, X, FSpectrumPalette[255]); end; end; Image1.Picture.Assign(FBitmap); end; end. My guess is that the way I fill the complex array with the signal data is not correct. Share this post Link to post
David Heffernan 2345 Posted November 29, 2021 I just use lapack for fft, seems not much point in trying to create yet another implementation Share this post Link to post
limelect 48 Posted November 29, 2021 This is what i have for FFT Old One .Good luck fft_source.zip Share this post Link to post
limelect 48 Posted November 29, 2021 And another one for Fourier ObjMath.zip Share this post Link to post
Pat Foley 51 Posted November 30, 2021 Here's a http://delphiforfun.org/programs/oscilloscope.htm program that reads audio from the sound card. The code uses a TP library. It compiles in Laz with a little work. 🙂 Share this post Link to post
Leif Uneus 43 Posted November 30, 2021 Look at this Scimark Delphi implementation that includes a FFT library. https://github.com/philip-goh/scimark-delphi/blob/master/FFT.pas Share this post Link to post
Wagner Landgraf 43 Posted December 1, 2021 Here is mine as well. This thread brought me back in time, this code is from 2003. 🙂 cronos-fft.zip Share this post Link to post