unit URecord; interface uses Classes, Math, SysUtils, {DXSounds, Wave, }UMusic, UIni, BASS; type TSound = class BufferNew: TMemoryStream; // buffer for newest sample BufferArray: array[1..4096] of smallint; // (Signal) newest 4096 samples BufferLong: array of TMemoryStream; // full buffer Num: integer; n: integer; // length of Signal to analyze // Spectrum: array[1..8192] of single; // sound buffer from above as FFT // Spektogram: array[0..100] of TSpekt; // FFT(t) // pitch detection SzczytJest: boolean; // czy jest szczyt Szczyt: integer; // pozycja szczytu na osi poziomej TonDokl: real; // ton aktualnego szczytu Ton: integer; // ton bez ulamka TonGamy: integer; // ton w gamie. wartosci: 0-11 Skala: real; // skala FFT // procedures procedure ProcessNewBuffer; procedure AnalizujBufor; // use to analyze sound from buffers to get new pitch procedure AnalizujByAutocorrelation; // we call it to analyze sound by checking Autocorrelation function AnalyzeAutocorrelationFreq(Freq: real): real; // use this to check one frequency by Autocorrelation function GetToneString: string; //from usdx 1.1 end; TSoundCardInput = record Name: string; end; TSoundCard = record // here can be the soundcard information - whole database from which user will select recording source Description: string; Input: array of TSoundCardInput; InputSeleceted: integer; // bass record BassRecordStream: hStream; end; TRecord = class SoundCard: array of TSoundCard; constructor Create; end; smallintarray = array [0..maxInt shr 1-1] of smallInt; psmallintarray = ^smallintarray; // procedures - bass record function GetMicrophone(handle: HSTREAM; buffer: Pointer; len: DWORD; user: DWORD): boolean; stdcall; var Sound: array of TSound; SoundCard: array of TSoundCard; Poz: integer; Recording: TRecord; const // from usdx 1.1 ToneStrings: array[0..11] of string = ( 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' ); implementation uses UMain, ULog; // from usdx 1.1 function TSound.GetToneString: string; begin if (SzczytJest) then Result := ToneStrings[TonGamy] + IntToStr(Ton div 12 + 2) else Result := '-'; end; procedure TSound.ProcessNewBuffer; var S: integer; L: integer; A: integer; begin // process BufferArray S := 0; L := BufferNew.Size div 2; if L > n then begin S := L - n; L := n; end; // copy to array for A := L+1 to n do BufferArray[A-L] := BufferArray[A]; BufferNew.Seek(2*S, soBeginning); BufferNew.ReadBuffer(BufferArray[1+n-L], 2*L); // process BufferLong if Ini.SavePlayback = 1 then begin BufferNew.Seek(0, soBeginning); BufferLong[0].CopyFrom(BufferNew, BufferNew.Size); end; end; procedure TSound.AnalizujBufor; begin AnalizujByAutocorrelation; end; procedure TSound.AnalizujByAutocorrelation; var T: integer; // tone F: real; // freq Wages: array[0..35] of real; // wages MaxT: integer; // max tone MaxW: real; // max wage V: real; // volume MaxV: real; // max volume S: integer; // Signal Threshold: real; // threshold begin // Log.LogAnalyze('[Analyze by Autocorrelation]'); SzczytJest := false; // find maximum volume of first 1024 words of signal MaxV := 0; for S := 1 to 1024 do begin // 0.5.2: fix. was from 0 to 1023 // Log.LogDebug('1'); // Log.LogDebug(IntTostr(S)); V := Abs(BufferArray[S]) / $10000; // Log.LogDebug('2'); // Log.LogDebug(IntTostr(S) + ': ' + FloatToStr(V) + ', MaxV='+floattostr(maxv)+', buf='+inttostr(length(BufferArray))); if V > MaxV then MaxV := V; // Log.LogDebug('3'); // Log.LogDebug(IntTostr(S) + ': ' + FloatToStr(V) + ', MaxV='+floattostr(maxv)+', buf='+inttostr(length(BufferArray))); end; // prepare to analyze MaxW := 0; MaxT := 0; // analyze all 12 halftones for T := 0 to 35 do begin // to 11, then 23, now 35 (for Whitney and my high voice) F := 130.81*Power(1.05946309436, T)/2; // let's analyze below 130.81 Wages[T] := AnalyzeAutocorrelationFreq(F); if Wages[T] > MaxW then begin // this frequency has better wage MaxW := Wages[T]; MaxT := T; end; end; // for T Threshold := 0.1; case Ini.Threshold of 0: Threshold := 0.05; 1: Threshold := 0.1; 2: Threshold := 0.15; 3: Threshold := 0.2; end; //Log.LogDebug('Sound -> AnalyzeByAutocorrelation: MaxV='+floattostr(maxv)+', Threshold='+floattostr(threshold)); if MaxV >= Threshold then begin // found acceptable volume // 0.1 SzczytJest := true; TonGamy := MaxT mod 12; Ton := MaxT; end; // Log.LogAnalyze('--> Weight: ') // Log.LogAnalyze('--> Selected: ' + BoolToStr(SzczytJest, true) + // ', TonGamy: ' + IntToStr(Ton) + // ', MaxV: ' + FloatToStr(MaxV)); // Log.LogAnalyze(''); end; function TSound.AnalyzeAutocorrelationFreq(Freq: real): real; // result medium difference var Count: real; Src: integer; Dst: integer; Move: integer; Il: integer; // how many counts were done begin // we use Signal as source Count := 0; Il := 0; Src := 1; Move := Round(44100/Freq); Dst := Src + Move; // ver 1 - sample 1 and compare n-times { while (Src <= Move) do begin // process by moving Src by one while (Dst < n) do begin // process up to n (4KB) of Signal Count := Count + Abs(Signal[Src] - Signal[Dst]) / $10000; Inc(Dst, Move); Inc(Il); end; Inc(Src); Dst := Src + Move; end;} // ver 2 - compare in vertical while (Dst < n) do begin // process up to n (4KB) of Signal Count := Count + Abs(BufferArray[Src] - BufferArray[Dst]) / $10000; Inc(Src); Inc(Dst); Inc(Il); end; Result := 1 - Count / Il; end; function GetMicrophone(handle: HSTREAM; buffer: Pointer; len: DWORD; user: DWORD): boolean; stdcall; var L: integer; S: integer; PB: pbytearray; PSI: psmallintarray; I: integer; Skip: integer; P1: integer; P2: integer; Boost: byte; begin // Log.LogDebug('Record -> GetMicrophone: len='+inttstr(len)); // set boost case Ini.MicBoost of 0: Boost := 1; 1: Boost := 2; 2: Boost := 4; 3: Boost := 8; else Boost := 1; end; // boost buffer L := Len div 2; // number of samples PSI := Buffer; for S := 0 to L-1 do begin I := PSI^[S] * Boost; if I > 32767 then I := 32767; // 0.5.0: limit if I < -32768 then I := -32768; // 0.5.0: limit PSI^[S] := I; end; // decode user P1 := (user and 255) - 1; P2 := (user div 256) - 1; // Log.LogDebug('Record -> GetMicrophone: P1='+inttostr(p1)+', P2='+inttostr(p2)); // 2 players USB mic, left channel if P1 >= 0 then begin L := Len div 4; // number of samples PB := Buffer; // Log.LogDebug('Record -> GetMicrophone -> Sound[P1].BufferNew.Clear'); Sound[P1].BufferNew.Clear; // 0.5.2: problem on exiting for S := 1 to L do begin Sound[P1].BufferNew.Write(PB[(S-1)*4], 2); end; Sound[P1].ProcessNewBuffer; end; // 2 players USB mic, right channel // if Ini.Debug = 0 then Skip := 2 // else Skip := 0; Skip := 2; if P2 >= 0 then begin L := Len div 4; // number of samples PB := Buffer; Sound[P2].BufferNew.Clear; for S := 1 to L do begin Sound[P2].BufferNew.Write(PB[Skip + (S-1)*4], 2); end; Sound[P2].ProcessNewBuffer; end; // Log.LogDebug('Record -> GetMicrophone -> Finish'); Result := true; end; constructor TRecord.Create; var Info: BASS_DEVICEINFO; SC: integer; // soundcard SCI: integer; // soundcard input Descr: string; InputName: string; Flags: integer; No: integer; Proceed: boolean; function isDuplicate(Desc: String): Boolean; var I: Integer; begin Result := False; //Check for Soundcard with same Description For I := 0 to SC-1 do begin if (SoundCard[I].Description = Desc) then begin Result := True; Break; end; end; end; begin // checks for recording devices and puts them into array; //Log.LogError('Init recoding devices...'); SetLength(SoundCard, 0); BASS_RecordFree; SC := 0; Proceed := BASS_RecordGetDeviceInfo(SC, &Info); while Proceed and not (info.flags and BASS_DEVICE_ENABLED=0) do begin Descr := Info.name; //If there is another SoundCard with the Same ID, Search an available Name if (IsDuplicate(Descr)) then begin No:= 1; //Count of SoundCards with same Name Repeat Inc(No) Until not IsDuplicate(Descr + ' (' + InttoStr(No) + ')'); //Set Description Descr := Descr + ' (' + InttoStr(No) + ')'; end; SetLength(SoundCard, SC+1); //Log.LogError('Device #' + IntToStr(SC+1) + ': ' + Descr); SoundCard[SC].Description := Descr; //check for recording inputs SCI := 0; BASS_RecordInit(SC); Flags := BASS_RecordGetInput(SCI, PSingle(nil)^); InputName := BASS_RecordGetInputName(SCI); //Log.LogError('Input #' + IntToStr(SCI) + ' (' + IntToStr(Flags) + '): ' + InputName); SetLength(SoundCard[SC].Input, 1); SoundCard[SC].Input[SCI].Name := InputName; // process each input while (Flags <> -1) and (Flags and BASS_INPUT_TYPE_MIC<>0) do begin if SCI >= 1 then begin SetLength(SoundCard[SC].Input, SCI+1); InputName := BASS_RecordGetInputName(SCI); SoundCard[SC].Input[SCI].Name := InputName; //Log.LogError('Input #' + IntToStr(SCI) + ' (' + IntToStr(Flags) + '): ' + InputName); end; Inc(SCI); Flags := BASS_RecordGetInput(SCI, PSingle(nil)^); end; BASS_RecordFree; Inc(SC); Proceed := BASS_RecordGetDeviceInfo(SC, &Info); end; // while //Log.LogError('End of Init recoding devices.'); end; end.