wp-xyz / Oscilloscope

3 stars 3 forks source link

Oscilloscope with uos audio engine. #2

Closed fredvs closed 3 years ago

fredvs commented 3 years ago

Hello.

I did a fork of your impressive project. https://github.com/fredvs/Oscilloscope It gives the choice to use Bass or uos audio engine.

With uos, the oscilloscope is working for all input file, mic and sine-wave. Spectrum meter is nearly working for all inputs.

To use uos engine, just copy the directory /Oscilloscope/source/3rdParty/uos/lib/ into the same directory as the executable.

If you are ok I would be happy to do a pull-request.

Fre;D

Capture d’écran_2021-01-30_19-22-41

Capture d’écran_2021-01-30_22-30-05

Capture d’écran_2021-01-30_22-28-36

wp-xyz commented 3 years ago

Interesting. Yes, please create a pull request.

fredvs commented 3 years ago

OK, super.

Note that I did not yet understand what kind of data is catch wit GetFFTData() used by Spectrum analyzer. I did try with float data but dont get the same result as Bass.

Also, in TuosDataCollector.GetWaveData(ABufPtr: Pointer; ABufSize: integer): integer is used a global variable AData: TChannelDataArray;

I did not catch how to assign it to ABufPtr.

The same for GetFFTData(), a global variable is used too: BData: array of single;

It works ok but I agree that those variables should not be used (like you did in your code).

wp-xyz commented 3 years ago

Give me some time, I have to recollect the details of this project...

fredvs commented 3 years ago

Yep, cool.

I just have done a last commit, I think it is ok now (apart for the Spectrum that is different vs Bass). Here it works very good on Linux. Not yet tested on Windows.

wp-xyz commented 3 years ago

I don't know what LookupTable is, probably an array of some float type (because you assign sin() to it). But then why do you call round for the triangle wave data ?

Am 30.01.2021 um 20:47 schrieb Fred van Stappen:

Also I think that the formula used in /uos/ for wave-triangle is wrong.

The formula for sine-wave and square-wave gives the same result as your code on the oscilloscope. But with code of uos, the triangle-wave is not so good as yours.

Here the code used in /uos/ to produce the waves:

Sine-wave: OK

|l:=1024; nPI_l:=2PI/l; for i:=0 to l-1 do begin if typewave = 0 then // sine begin if channel = 1 then StreamIn[x].Data.LookupTableLeft[i]:=sin(inPI_l); if channel = 2 then StreamIn[x].Data.LookupTableRight[i]:=sin(i*nPI_l); end; |

Square-wave: OK

|if typewave = 1 then // square begin if channel = 1 then begin if sin(inPI_l) >= 0 then StreamIn[x].Data.LookupTableLeft[i]:= 1 else StreamIn[x].Data.LookupTableLeft[i]:=-1 ; end; if channel = 2 then begin if sin(inPI_l) >= 0 then StreamIn[x].Data.LookupTableRight[i]:= 1 else StreamIn[x].Data.LookupTableRight[i]:=-1 ; end; end; |

Triangle-wave: NOT OK:

|if typewave = 2 then // triangle begin if channel = 1 then begin StreamIn[x].Data.LookupTableLeft[i]:= (round((l - i)/(l/2)) -1); end; if channel = 2 then begin StreamIn[x].Data.LookupTableRight[i]:= (round((l - i)/(l/2)) -1); end; end; |

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/wp-xyz/Oscilloscope/issues/2#issuecomment-770270771, or unsubscribe https://github.com/notifications/unsubscribe-auth/AHK5WDAC2QALO7J76PT4HRLS4RO57ANCNFSM4W2RCTGQ.

fredvs commented 3 years ago

Huh, I did delete that post on issue because there was indeed error. (what did that round() there?) uos was updated 2 days ago so that message has no more sense.

Here the updated uos source:

if typewave = 2 then // triangle
  begin
   if channel = 1 then
   begin
    StreamIn[x].Data.LookupTableLeft[i]:= ((l - i)/(l/2))-1;
   end; 
   if channel = 2 then
   begin
   StreamIn[x].Data.LookupTableRight[i]:= ((l - i)/(l/2))-1;
    end; 
  end;

By the way, uos triangle has not exactly same shape as yours, yours seems more like a pyramid, uos like a tooth. Maybe I will add a typewave-pyramid in uos.

wp-xyz commented 3 years ago

Fixed usage of the data array for the uos wave data, no more global array. The recorder mode is working now, tested Windows and Linux. A remaining problem is that the display is not very stable: when I move the mouse the trace disappears for a short moment. This does not happen with BASS.

Note: I changed the location of the libs to be inside the bin folder directly. So far only for Windows and Linux, but I am planning to do this for the others as well. Since the compiler creates separate output directories for each OS, and since the uos libs have unique names regarding the bitness, I think there is no need for a lib/os/bitness subfolders.

fredvs commented 3 years ago

Hello.

Nice that it works now. Strange that display not stable. Here on Linux I get the same stability as Bass. So maybe there is a problem with fpc thread, I dont know, I have to find a Windows machine to test.

About the spectrum, ok, then Bass dont gives ram samples but FFT of the samples. uos gives FFT filters, lowpass, highpass, bandpass,...

I use it for doing sprectum analysis of some frequencies and to modify it with a equalizer. Maybe you may use this or you may do your own fft, there is a module in uos for this.

Capture d’écran_2021-02-01_00-47-32

About location of libs I agree, it is not easy to find the best way...

fredvs commented 3 years ago

Note also that to produce the waves, I did use the build-in synthesizer of uos. I have to re-anyalize your code, uos can deal for input with memory buffer too (of your own samples).

wp-xyz commented 3 years ago

Maybe you may use this or you may do your own fft, there is a module in uos for this.

I saw that there is a TFFT object in uos_dsp_noiseremoval, but have no idea how to apply it. Could you call it in the oSpectrumFrame.GetFFTData? It must return the amplitudes as single array (values normalized to 1) alternating between channels (even indices = left channel, odd indices = right channel). The array index denotes the frequency: Index 0 = DC component in left channel, Index 1 = DC component in right channel, Index 2 = lowest frequency in left channel, index 3 =lowest frequency in right channel etc. (My earlier statement about the "strange" ordering was not correct).

It's ok when you store in a global array, I can adjust to the memory allocated later.

fredvs commented 3 years ago

I did not catch yet what data Bass gives with GetFFTData().

uos uses for spectrum meter the values of certain defined frequencies in float ( from 0 to 1). But you have to define the frequencies that you want to measure.

It is what I dont catch with Bass, what frequencies is given in the buffer?

fredvs commented 3 years ago

To get the value of defined frequencies, I use uos_InputAddFilter, like this:

for i := 1 to 10 do // 10 frequencies spectrum BandPass
              uos_InputAddFilter(theplayer, InputIndex1,
                3, Equalizer_Bands[i].lo_freq, Equalizer_Bands[i].hi_freq, 1,
                3, Equalizer_Bands[i].lo_freq, Equalizer_Bands[i].hi_freq, 1, False, nil);

The frequencies:

Equalizer_Bands[1].lo_freq  := 18;
  Equalizer_Bands[1].hi_freq  := 46;
  Equalizer_Bands[1].Text     := '31';
  Equalizer_Bands[2].lo_freq  := 47;
  Equalizer_Bands[2].hi_freq  := 94;
  Equalizer_Bands[2].Text     := '62';
  Equalizer_Bands[3].lo_freq  := 95;
  Equalizer_Bands[3].hi_freq  := 188;
  Equalizer_Bands[3].Text     := '125';
  Equalizer_Bands[4].lo_freq  := 189;
  Equalizer_Bands[4].hi_freq  := 375;
  Equalizer_Bands[4].Text     := '250';
  Equalizer_Bands[5].lo_freq  := 376;
  Equalizer_Bands[5].hi_freq  := 750;
  Equalizer_Bands[5].Text     := '500';
  Equalizer_Bands[6].lo_freq  := 751;
  Equalizer_Bands[6].hi_freq  := 1500;
  Equalizer_Bands[6].Text     := '1K';
  Equalizer_Bands[7].lo_freq  := 1501;
  Equalizer_Bands[7].hi_freq  := 3000;
  Equalizer_Bands[7].Text     := '2K';
  Equalizer_Bands[8].lo_freq  := 3001;
  Equalizer_Bands[8].hi_freq  := 4000;
  Equalizer_Bands[8].Text     := '4K';
  Equalizer_Bands[9].lo_freq  := 4001;
  Equalizer_Bands[9].hi_freq  := 6000;
  Equalizer_Bands[9].Text     := '6K';
  Equalizer_Bands[10].lo_freq := 6001;
  Equalizer_Bands[10].hi_freq := 16000;
  Equalizer_Bands[10].Text    := '16K';
fredvs commented 3 years ago

Post before is to define the value.

To get the value, here example how to get freq value (form 0 to 1) in the procedure in _uosLoopProcIn():

thearray := uos_InputFiltersGetLevelArray(theplayer, InputIndex1);
      x        := 0;
      i        := 0;
      while x < length(thearray) - 1 do
      begin
        arl[i] := thearray[x];
        arr[i] := thearray[x + 1];
        x      := x + 2;
        Inc(i);
      end;

      spectrum1fo.tchartleft.traces[0].ydata  := arl;
      spectrum1fo.tchartright.traces[0].ydata := arr;

But for this I have to know what frequencies to measure.

fredvs commented 3 years ago

Re-hello.

Here code used in StrumPract project that have a equalizer 10 bands, a spectrum view 10 bands, wave-form design, position, volume VU (no timer used):

procedure tsongplayerfo.doplayerstart(const Sender: TObject);
var
  samformat, hassent: shortint;
  ho, mi, se, ms: word;
  fileex: string;
  i: integer;
begin
  if Caption = 'Player 1' then
  begin
    fileex := fileext(PChar(ansistring(historyfn.Value)));

    if (fileex = 'wav') or (fileex = 'WAV') or (fileex = 'ogg') or (fileex = 'OGG') or (fileex = 'flac') or
      (fileex = 'FLAC') or (fileex = 'mp3') or (fileex = 'MP3') then
    begin

      if fileexists(historyfn.Value) then
      begin
        samformat := 0;

        // PlayerIndex : from 0 to what your computer can do ! (depends of ram, cpu, ...)
        // If PlayerIndex exists already, it will be overwritten...

        uos_Stop(theplayer); // done by  uos_CreatePlayer() but faster if already done before (no check)

        if uos_CreatePlayer(theplayer) then
          // Create the player.
          // PlayerIndex : from 0 to what your computer can do !
          // If PlayerIndex exists already, it will be overwriten...

          Inputindex1 := uos_AddFromFile(theplayer, PChar(ansistring(historyfn.Value)), -1,
            samformat, 1024 * 8);

        // add input from audio file with custom parameters
        // FileName : filename of audio file
        // PlayerIndex : Index of a existing Player
        // OutputIndex : OutputIndex of existing Output // -1 : all output, -2: no output, other integer : existing output)
        // SampleFormat : -1 default : Int16 : (0: Float32, 1:Int32, 2:Int16) SampleFormat of Input can be <= SampleFormat float of Output
        // FramesCount : default : -1 (65536 div channels)
        //  result : -1 nothing created, otherwise Input Index in array

        if Inputindex1 > -1 then
        begin
          // Outputindex1 := uos_AddIntoDevOut(Playerindex1) ;
          // add a Output into device with default parameters

          if configfo.latplay.Value < 0 then
            configfo.latplay.Value := -1;

          Outputindex1 := uos_AddIntoDevOut(theplayer, configfo.devoutcfg.Value, configfo.latplay.Value, uos_InputGetSampleRate(theplayer, Inputindex1),
            uos_InputGetChannels(theplayer, Inputindex1), samformat, 1024 * 8, -1);

          // Add a Output into Device Output
          // Device ( -1 is default device )
          // Latency  ( -1 is latency suggested )
          // SampleRate : delault : -1 (44100)
          // Channels : delault : -1 (2:stereo) (0: no channels, 1:mono, 2:stereo, ...)
          // SampleFormat : default : -1 (1:Int16) (0: Float32, 1:Int32, 2:Int16)
          // FramesCount : default : -1 (= 65536)
          // ChunkCount : default : -1 (= 512)
          //  result :  Output Index in array  -1 = error

          uos_InputSetLevelEnable(theplayer, Inputindex1, 2);
          // set calculation of level/volume (usefull for showvolume procedure)
          // set level calculation (default is 0)
          // 0 => no calcul
          // 1 => calcul before all DSP procedures.
          // 2 => calcul after all DSP procedures.
          // 3 => calcul before and after all DSP procedures.

          uos_InputSetPositionEnable(theplayer, Inputindex1, 1);
          // set calculation of position (usefull for positions procedure)
          // set position calculation (default is 0)
          // 0 => no calcul
          // 1 => calcul position.

          uos_LoopProcIn(theplayer, Inputindex1, @LoopProcPlayer1);
          // Assign the procedure of object to execute inside the loop
          // PlayerIndex : Index of a existing Player
          // Inputindex1 : Index of a existing Input
          // LoopProcPlayer1 : procedure of object to execute inside the loop

          uos_InputAddDSPVolume(theplayer, Inputindex1, 1, 1);
          // DSP Volume changer
          // Playerindex1 : Index of a existing Player
          // Inputindex1 : Index of a existing input
          // VolLeft : Left volume
          // VolRight : Right volume

          uos_InputSetDSPVolume(theplayer, Inputindex1,
            (edvolleft.Value / 100) * commanderfo.genvolleft.Value * 1.5, (edvolright.Value / 100) * commanderfo.genvolright.Value * 1.5, True);
          /// Set volume
          // Playerindex1 : Index of a existing Player
          // Inputindex1 : InputIndex of a existing Input
          // VolLeft : Left volume
          // VolRight : Right volume
          // Enable : Enabled

          // This is a other custom DSP...stereo to mono  to show how to do a DSP ;-)
          DSPindex11 := uos_InputAddDSP(theplayer, Inputindex1, nil, @DSPStereo2Mono, nil, nil);
          uos_InputSetDSP(theplayer, Inputindex1, DSPindex11, setmono.Value);

          for i := 1 to 10 do // equalizer
            Equalizer_Bands[i].theindex :=
              uos_InputAddFilter(theplayer, InputIndex1,
              1, Equalizer_Bands[i].lo_freq, Equalizer_Bands[i].hi_freq, 1,
              1, Equalizer_Bands[i].lo_freq, Equalizer_Bands[i].hi_freq, 1, True, nil);

          equalizerfo1.onchangeall();

          if commanderfo.speccalc.Value = True then
            for i := 1 to 10 do // spectrum BandPass
              uos_InputAddFilter(theplayer, InputIndex1,
                3, Equalizer_Bands[i].lo_freq, Equalizer_Bands[i].hi_freq, 1,
                3, Equalizer_Bands[i].lo_freq, Equalizer_Bands[i].hi_freq, 1, False, nil);

            { // add bs2b plugin with samplerate_of_input1 / default channels (2 = stereo)
  if plugbs2b = true then
  begin
   PlugInindex1 := uos_AddPlugin(Playerindex1, 'bs2b',
   uos_InputGetSampleRate(Playerindex1, Inputindex1) , -1);
   uos_SetPluginbs2b(Playerindex1, Pluginindex1, -1 , -1, -1, chkst2b.value);
  end;
   }
          /// add SoundTouch plugin with samplerate of input1 / default channels (2 = stereo)
          /// SoundTouch plugin should be the last added.
          if plugsoundtouch = True then
          begin
            PluginIndex2 := uos_AddPlugin(theplayer, 'soundtouch', uos_InputGetSampleRate(theplayer, Inputindex1), -1);
            ChangePlugSetSoundTouch(self); // custom procedure to Change plugin settings
          end;

          Inputlength1 := uos_Inputlength(theplayer, Inputindex1);
          // Length of Input in samples

          tottime1 := uos_InputlengthTime(theplayer, Inputindex1);
          // Length of input in time

          DecodeTime(tottime1, ho, mi, se, ms);

          totsec1 := (ho * 3600) + (mi * 60) + se;

          llength.Value := utf8decode(format('%.2d:%.2d:%.2d.%.3d', [ho, mi, se, ms]));

          DSPindex1 := uos_InputAddDSP(theplayer, Inputindex1, @DSPReverseBefore1, @DSPReverseAfter, nil, nil);
          // add a custom DSP procedure for input
          // Playerindex1 : Index of a existing Player
          // Inputindex1: InputIndex of existing input
          // BeforeFunc : function to do before the buffer is filled
          // AfterFunc : function to do after the buffer is filled
          // EndedFunc : function to do at end of thread
          // LoopProc : external procedure to do after the buffer is filled

          // set the parameters of custom DSP
          //  playreverse.value := false;

          uos_InputSetDSP(theplayer, Inputindex1, DSPindex1, playreverse.Value);

          uos_EndProc(theplayer, @ClosePlayer1);
          /// procedure to execute when stream is terminated
          // Assign the procedure of object to execute at end
          // PlayerIndex : Index of a existing Player
          // ClosePlayer1 : procedure of object to execute inside the general loop

          btinfos.Enabled := True;

          hasmixed1 := False;

          trackbar1.Value   := 0;
          trackbar1.Enabled := True;

          wavefo.trackbar1.Value   := 0;
          wavefo.container.frame.scrollpos_x := 0;
          wavefo.trackbar1.Enabled := True;
          wavefo.Caption           := 'Wave1 of ' + historyfn.Value;

          theplaying1 := historyfn.Value;

          if vuinvar then
          begin
            vuLeft.Visible  := True;
            vuRight.Visible := True;
            commanderfo.vuLeft.Visible := True;
            commanderfo.vuRight.Visible := True;
          end;

          with commanderfo do
          begin
            btnStop.Enabled   := True;
            btnresume.Enabled := False;
            btnresume.Visible := False;
            if cbloop.Value = True then
            begin
              btnPause.Enabled := False;
              btnPause.Visible := True;
            end
            else
            begin
              btnPause.Visible := True;
              btnPause.Enabled := True;
            end;

          end;

uos_Play(theplayer); 
wp-xyz commented 3 years ago

Huuuh, too much for an old man...

A time dependent sample with N points can be described as a combination of N/2 sine and N/2 cos waves:

signal(t) = sum(Ai sin(fit)) + sum(Bi cos(fit)

Ai and Bi are the amplitudes of these contributing waves of frequencies fi. The lowest frequency f0 is the DC value of the input signal (average of all samples), frequency f0 = 0 The next frequency is one period of a wave extending over the full sample. i.d. when there are N samples taken at a rate R samples per second the these samples cover a time of T1 = N/R seconds; thus, the lowest nonzero frequency is 1 / T1 = 1 * R/N. The next frequency has two oscillations over the sample, the corresponding frequency is 2R/N etc. The highest frequency is the one where an oscillation fits between two adjacent points, it is half of the sampling frequency R.

All these oscillations have different amplitudes, and it is the aim of the Fourier Transform to calculate these amplitudes. FFT is just a special algorithm to calculate this.

fredvs commented 3 years ago

saw that there is a TFFT object in uos_dsp_noiseremoval, but have no idea how to apply it. Could you call it in the oSpectrumFrame.GetFFTData?

You may take a look at uos_InputAddDSP(). uos_InputAddDSP() is used to deal with FFT filters.

 uos_InputAddDSP(Playerindex, Inputindex, BeforeFunc, AfterFunc, EndedFunc, LoopProc);
          // add a custom DSP procedure for input
          // Playerindex : Index of a existing Player
          // Inputindex: InputIndex of existing input
          // BeforeFunc : function to do before the buffer is filled
          // AfterFunc : function to do after the buffer is filled
          // EndedFunc : function to do at end of thread
          // LoopProc : external procedure to do after the buffer is filled

For example InputAddFilter(...) use this: InputAddDSP(InputIndex, nil, @uos_BandFilter, nil, LoopProc);

You may take a look at function _uos_BandFilter(var Data: Tuos_Data; var fft: TuosFFT): TDArFloat; It is the AfterFunc used in procedure InputAddFilter().

I hope you are still there after all that bla-bla. ;-)

fredvs commented 3 years ago

Huuuh, too much for an old man...

A time dependent sample with N points can be described as a combination of N/2 sine and N/2 cos waves:

signal(t) = sum(Ai sin(fi_t)) + sum(Bi cos(fi_t)

Ai and Bi are the amplitudes of these contributing waves of frequencies fi. The lowest frequency f0 is the DC value of the input signal (average of all samples), frequency f0 = 0 The next frequency is one period of a wave extending over the full sample. i.d. when there are N samples taken at a rate R samples per second the these samples cover a time of T1 = N/R seconds; thus, the lowest nonzero frequency is 1 / T1 = 1 * R/N. The next frequency has two oscillations over the sample, the corresponding frequency is 2R/N etc. The highest frequency is the one where an oscillation fits between two adjacent points, it is half of the sampling frequency R.

All these oscillations have different amplitudes, and it is the aim of the Fourier Transform to calculate these amplitudes. FFT is just a special algorithm to calculate this.

Ooops that very complicated for a older man like me.

Ok, I will deeply study your post and try to understand. Write you later...

Thanks.

wp-xyz commented 3 years ago

For example InputAddFilter(...) use this: InputAddDSP(InputIndex, nil, @uos_BandFilter, nil, LoopProc);

You may take a look at function _uos_BandFilter(var Data: Tuos_Data; var fft: TuosFFT): TDArFloat; It is the AfterFunc used in procedure InputAddFilter().

If I remember university filtering using FFT consists of these steps:

Therefore, the FFT must be at the beginning of your filtering process.

wp-xyz commented 3 years ago

Now I made progress with the FFT routines provided by uos.

In the attached project I am synthesizing a wave by a superposition of four sine waves at frequencies 1 kHz, 3kHz, 5 kHz and 7kHz. When the amplitudes of these sine waves are in the same ratio as the frequencies (1:3:5:7) the result are the first terms of the fourier expansion of a square waves (if I'd add more terms following this odd-numbers-only rule the result would become more and more square).

I use this wave as input for an FFT routine on the basis of the TFFT object, and the plot of the spectrum, in fact, shows the input frequencies at the correct amplitude ratios. There is still an issue, though, because the tails of the FFT peaks are very noisy, as if the indexes are not selected properly. I'll have to look at this in detail tomorrow because the data points are scrambled in some hard-to-understand way.

One question, maybe you know the answer: Since the input data of an FFT are complex numbers having a real and an imaginary part, I took the calculated wave as real part and extended it by zeros to twice length for the imaginary part. Since we have two channels it is an idea to combine both channels to the input array of the FFT, i.e. left channel = 1st half of the data, right channel = 2nd half of the data, and to do the FFT for both channels simultaneously. Do you know whether this is possible with the uos TFFT object?

fft.zip

fredvs commented 3 years ago

Huh, to be totally fair...

I am absolutely impressed by the Fast Fourier Transform but I am a for-ever beginner with it.

All the fft and filter part of uos was done by Andrei Borovsky and I have no new from him. I can use the fft's but I am totally unable to do one.

Of course uos can deal with 2 channels with fft but I dont really know much more.

wp-xyz commented 3 years ago

Am 01.02.2021 um 23:34 schrieb Fred vS:

Huh, to be totally fair...

I am absolutely impressed by the Fast Fourier Transform but I am a for-ever beginner with it.

All the fft and filter part of uos was done by Andrei Borovsky and I have no new from him. I can use the fft's but I am totally unable to do one.

Of course uos can deal with 2 channels with fft but I dont really know much more.

No problem. I'll read "Numerical recipes" again, they brought up the idea to transform both channels simultaneously - a nice time saver. I would be surprised if uos would not do this, too.

fredvs commented 3 years ago

Am 01.02.2021 um 23:34 schrieb Fred vS: Huh, to be totally fair... I am absolutely impressed by the Fast Fourier Transform but I am a for-ever beginner with it. All the fft and filter part of uos was done by Andrei Borovsky and I have no new from him. I can use the fft's but I am totally unable to do one. Of course uos can deal with 2 channels with fft but I dont really know much more. No problem. I'll read "Numerical recipes" again, they brought up the idea to transform both channels simultaneously - a nice time saver. I would be surprised if uos would not do this, too.

Imho, because with uos you have access to the root (the samples) and also to the devices, all can be done, there is no limit.

And if, by chance, somebody has something to propose, ( huh fft's for example ), he is more than welcome.

;-)

fredvs commented 3 years ago

fft.zip

Ho, you did it...

WOW.

Capture d’écran_2021-02-02_01-04-19

wp-xyz commented 3 years ago

Not exactly. When you zoom in (drag a rectangle from top-left to right-bottom corner) near the foot of the peaks you see an up and down of the points.

In the meantime i found out that the data array must be filled in a different way: it must be filled by pairs, not half by half, i.e. I add a data value, then zero,then next data value, zero, etc. This results in a smooth curve, and this is definitely something which we can use.

Now I am trying to do an FFT for the two-channel data. My first question: Is it true that the input wave is alternating between left and right values? I.e.: the first value is left, next one right, then left again, right again etc. (What about mono data BTW: do thex contain only the single values per sample, or is it a pseudo-stereo signal with left=right).

Next question: I am trying to find examples how the FFT is applied in the uos filtering routines, and there is one in TNoiseRemoval.FillFirstHistoryWindow. The stereo channels are not mentioned here, and this makes me believe that the FFT handles only the sum of the two signals. So, when you filter an audio signal by these routines can you treat the channels separately? Or are the settings applied to both channels?

fredvs commented 3 years ago

Is it true that the input wave is alternating between left and right values? I.e.: the first value is left, next one right, then left again, right again etc.

Not sure to understand. PCM data are stored that way: if stereo (L = left channel, R = Right channel): LRLRLRLRLRLRLR..... For mono signal, there is only one channel, so LLLLLLLLL.... If the data has 3 channels, 123123123123123....

A frame is a combination of the numbers of channels, num of frames = total samples div num channels.

About TNoiseRemoval (done by Andrew) I need to jump into his code, I will do it tonight.