Closed fredvs closed 3 years ago
Interesting. Yes, please create a pull request.
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).
Give me some time, I have to recollect the details of this project...
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.
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.
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.
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.
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.
About location of libs I agree, it is not easy to find the best way...
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).
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.
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?
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';
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.
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);
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.
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. ;-)
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.
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.
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?
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.
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.
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.
;-)
fft.zip
Ho, you did it...
WOW.
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?
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.
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