unit URecord;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses Classes,
Math,
SysUtils,
UCommon,
UMusic,
UIni;
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
// 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
end;
TSoundCardInput = record
Name: string;
end;
TGenericSoundCard = class
// here can be the soundcard information - whole database from which user will select recording source
Description: string; // soundcard name/description
Input: array of TSoundCardInput; // soundcard input(-source)s
InputSelected: integer; // unused. What is this good for?
MicInput: integer; // unused. What is this good for?
//SampleRate: integer; // TODO: for sample-rate conversion (for devices that do not support 44.1kHz)
CaptureSoundLeft: TSound; // sound(-buffer) used for left channel capture data
CaptureSoundRight: TSound; // sound(-buffer) used for right channel capture data
end;
TRecord = class
Sound: array of TSound;
SoundCard: array of TGenericSoundCard;
constructor Create;
// handle microphone input
procedure HandleMicrophoneData(Buffer: Pointer; Length: Cardinal;
InputDevice: TGenericSoundCard);
end;
smallintarray = array [0..maxInt shr 1-1] of smallInt;
psmallintarray = ^smallintarray;
var
Poz: integer;
Recording: TRecord;
implementation
uses UMain;
// FIXME: Race-Conditions between Callback-thread and main-thread
// on BufferArray (maybe BufferNew also).
// Use SDL-mutexes to solve this problem.
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
SzczytJest := false;
// find maximum volume of first 1024 words of signal
MaxV := 0;
for S := 1 to 1024 do // 0.5.2: fix. was from 0 to 1023
begin
V := Abs(BufferArray[S]) / $10000;
if V > MaxV then
MaxV := V;
end;
// prepare to analyze
MaxW := 0;
// analyze all 12 halftones
for T := 0 to 35 do // to 11, then 23, now 35 (for Whitney and my high voice)
begin
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;
if MaxV >= Threshold then
begin // found acceptable volume // 0.1
SzczytJest := true;
TonGamy := MaxT mod 12;
Ton := MaxT mod 12;
end;
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 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;
{*
* Handle captured microphone input data.
* Params:
* Buffer - buffer of signed 16bit interleaved stereo PCM-samples.
* Interleaved means that a right-channel sample follows a left-
* channel sample and vice versa (0:left[0],1:right[0],2:left[1],...).
* Length - number of bytes in Buffer
* Input - Soundcard-Input used for capture
*}
procedure TRecord.HandleMicrophoneData(Buffer: Pointer; Length: Cardinal;
InputDevice: TGenericSoundCard);
var
L: integer;
S: integer;
PB: pbytearray;
PSI: psmallintarray;
I: integer;
Skip: integer;
Boost: byte;
begin
// set boost
case Ini.MicBoost of
0: Boost := 1;
1: Boost := 2;
2: Boost := 4;
3: Boost := 8;
end;
// boost buffer
L := Length 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;
// 2 players USB mic, left channel
if InputDevice.CaptureSoundLeft <> nil then
begin
L := Length div 4; // number of samples
PB := Buffer;
InputDevice.CaptureSoundLeft.BufferNew.Clear; // 0.5.2: problem on exiting
for S := 0 to L-1 do
begin
InputDevice.CaptureSoundLeft.BufferNew.Write(PB[S*4], 2);
end;
InputDevice.CaptureSoundLeft.ProcessNewBuffer;
end;
// 2 players USB mic, right channel
Skip := 2;
if InputDevice.CaptureSoundRight <> nil then
begin
L := Length div 4; // number of samples
PB := Buffer;
InputDevice.CaptureSoundRight.BufferNew.Clear;
for S := 0 to L-1 do
begin
InputDevice.CaptureSoundRight.BufferNew.Write(PB[Skip + S*4], 2);
end;
InputDevice.CaptureSoundRight.ProcessNewBuffer;
end;
end;
constructor TRecord.Create;
var
S: integer;
begin
SetLength(Sound, 6 {max players});//Ini.Players+1);
for S := 0 to High(Sound) do begin //Ini.Players do begin
Sound[S] := TSound.Create;
Sound[S].Num := S;
Sound[S].BufferNew := TMemoryStream.Create;
SetLength(Sound[S].BufferLong, 1);
Sound[S].BufferLong[0] := TMemoryStream.Create;
Sound[S].n := 4*1024;
end;
end;
end.