From f786f747554c79de3a7aa1fd63cd1e7c2e0763c3 Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Wed, 5 Mar 2008 11:16:44 +0000
Subject: FFT support for SDL/Portaudio playback

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@909 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 Game/Code/Classes/UAudioInput_Portaudio.pas    |   7 +-
 Game/Code/Classes/UAudioPlayback_SoftMixer.pas | 124 ++++++++++++++++++-------
 Game/Code/Classes/UMusic.pas                   |   7 +-
 Game/Code/Screens/UScreenOptionsRecord.pas     |  12 +--
 4 files changed, 106 insertions(+), 44 deletions(-)

(limited to 'Game/Code')

diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
index 74c8d98f..077598a9 100644
--- a/Game/Code/Classes/UAudioInput_Portaudio.pas
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -149,6 +149,7 @@ var
   stream:      PPaStream;
   streamInfo:  PPaStreamInfo;
   sampleRate:  integer;
+  latency:     TPaTime;
   {$IFDEF UsePortmixer}
   mixer:       PPxMixer;
   {$ENDIF}
@@ -210,13 +211,17 @@ begin
     else
       sampleRate := 44100;
 
+    // on vista and xp the defaultLowInputLatency may be set to 0 but it works.
+    // TODO: correct too low latencies (what is a too low latency, maybe < 10ms?)
+    latency := deviceInfo^.defaultLowInputLatency;
+
     // setup desired input parameters
     with inputParams do
     begin
       device := deviceIndex;
       channelCount := channelCnt;
       sampleFormat := paInt16;
-      suggestedLatency := deviceInfo^.defaultLowInputLatency;
+      suggestedLatency := latency;
       hostApiSpecificStreamInfo := nil;
     end;
 
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
index e145587c..5beff682 100644
--- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
+++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
@@ -24,6 +24,7 @@ type
 
       DecodeStream: TAudioDecodeStream;
       SampleBuffer    : PChar;
+      SampleBufferCount: integer; // number of available bytes in SampleBuffer
       SampleBufferPos : cardinal;
       BytesAvail: integer;
       cvt: TSDL_AudioCVT;
@@ -65,6 +66,8 @@ type
       function GetPosition: real;
       procedure SetPosition(Time: real);
       function ReadData(Buffer: PChar; BufSize: integer): integer;
+
+      procedure GetFFTData(var data: TFFTData);
   end;
 
   TAudioMixerStream = class
@@ -507,43 +510,50 @@ begin
   if (remFrameBytes > 0) then
     decodeBufSize := decodeBufSize + (frameSize - remFrameBytes);
 
-  // calc buffer size
-  sampleBufSize := decodeBufSize * cvt.len_mult;
-
-  // resize buffer if necessary.
-  // The required buffer-size will be smaller than the result-buffer
-  // in most cases (if the decoded signal is mono or has a lesser bitrate).
-  // If the output-rate is 44.1kHz and the decode-rate is 48kHz or 96kHz it
-  // will be ~1.09 or ~2.18 times bigger. Those extra memory consumption
-  // should be reasonable. If not we should call TDecodeStream.ReadData()
-  // multiple times.
-  // Note: we do not decrease the buffer by the count of bytes used from
-  //   the previous call of this function (bytesAvail). Otherwise the
-  //   buffer will be reallocated each time this function is called just to
-  //   add or remove a few bytes from the buffer.
-  //   By not doing this the buffer's size should be rather stable and it
-  //   will not be reallocated/resized at all if the BufSize params does not
-  //   change in consecutive calls.
-  ReallocMem(SampleBuffer, sampleBufSize);
-  if not assigned(SampleBuffer) then
-    Exit;
+  Lock();
+  try
+    // calc buffer size
+    sampleBufSize := decodeBufSize * cvt.len_mult;
+
+    // resize buffer if necessary.
+    // The required buffer-size will be smaller than the result-buffer
+    // in most cases (if the decoded signal is mono or has a lesser bitrate).
+    // If the output-rate is 44.1kHz and the decode-rate is 48kHz or 96kHz it
+    // will be ~1.09 or ~2.18 times bigger. Those extra memory consumption
+    // should be reasonable. If not we should call TDecodeStream.ReadData()
+    // multiple times.
+    // Note: we do not decrease the buffer by the count of bytes used from
+    //   the previous call of this function (bytesAvail). Otherwise the
+    //   buffer will be reallocated each time this function is called just to
+    //   add or remove a few bytes from the buffer.
+    //   By not doing this the buffer's size should be rather stable and it
+    //   will not be reallocated/resized at all if the BufSize params does not
+    //   change in consecutive calls.
+    ReallocMem(SampleBuffer, sampleBufSize);
+    if not assigned(SampleBuffer) then
+      Exit;
 
-  // decode data
-  nBytesDecoded := DecodeStream.ReadData(SampleBuffer, decodeBufSize);
-  if (nBytesDecoded = -1) then
-    Exit;
+    // decode data
+    nBytesDecoded := DecodeStream.ReadData(SampleBuffer, decodeBufSize);
+    if (nBytesDecoded = -1) then
+      Exit;
 
-  // end-of-file reached -> stop playback
-  if (DecodeStream.EOF) then
-    Stop();
+    // end-of-file reached -> stop playback
+    if (DecodeStream.EOF) then
+      Stop();
 
-  // resample decoded data
-  cvt.buf := PUint8(SampleBuffer);
-  cvt.len := nBytesDecoded;
-  if (SDL_ConvertAudio(@cvt) = -1) then
-    Exit;
+    // resample decoded data
+    cvt.buf := PUint8(SampleBuffer);
+    cvt.len := nBytesDecoded;
+    if (SDL_ConvertAudio(@cvt) = -1) then
+      Exit;
+
+    SampleBufferCount := cvt.len_cvt;
+  finally
+    Unlock();
+  end;
 
-  BytesAvail := cvt.len_cvt;
+  BytesAvail := SampleBufferCount;
   SampleBufferPos := 0;
 
   // copy data to result buffer
@@ -556,6 +566,50 @@ begin
   Result := BufSize - BytesNeeded;
 end;
 
+procedure TSoftMixerPlaybackStream.GetFFTData(var data: TFFTData);
+var
+  i: integer;
+  Frames: integer;
+  DataIn: PSingleArray;
+  AudioFormat: TAudioFormatInfo;
+begin
+  // only works with SInt16 and Float values at the moment
+  AudioFormat := Engine.GetAudioFormatInfo();
+
+  GetMem(DataIn, FFTSize * SizeOf(Single));
+
+  Lock();
+  // FIXME: We just use the first Frames frames, the others are ignored.
+  // This is OK for the equalizer display but not if we want to use
+  // this function for voice-analysis.
+  Frames := Min(FFTSize, SampleBufferCount div AudioFormat.FrameSize);
+  // use only first channel and convert data to float-values
+  case AudioFormat.Format of
+    asfS16:
+    begin
+      for i := 0 to Frames-1 do
+        DataIn[i] := PSmallInt(@SampleBuffer[i*AudioFormat.FrameSize])^ / -Low(SmallInt);
+    end;
+    asfFloat:
+    begin
+      for i := 0 to Frames-1 do
+        DataIn[i] := PSingle(@SampleBuffer[i*AudioFormat.FrameSize])^;
+    end;
+  end;
+  Unlock();
+
+  WindowFunc(FFTSize, DataIn);
+  PowerSpectrum(FFTSize, DataIn, @data);
+  FreeMem(DataIn);
+
+  // resize data to a 0..1 range
+  for i := 0 to High(TFFTData) do
+  begin
+    // TODO: this might need some work
+    data[i] := Sqrt(data[i]) / 100;
+  end;
+end;
+
 (* TODO: libsamplerate support
 function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
 var
@@ -778,8 +832,8 @@ end;
 //Equalizer
 procedure TAudioPlayback_SoftMixer.GetFFTData(var data: TFFTData);
 begin
-  //Get Channel Data Mono and 256 Values
-//  BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
+  if assigned(MusicStream) then
+    MusicStream.GetFFTData(data);
 end;
 
 // Interface for Visualizer
diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas
index 44ae2a61..f0bf3a9c 100644
--- a/Game/Code/Classes/UMusic.pas
+++ b/Game/Code/Classes/UMusic.pas
@@ -85,10 +85,13 @@ type
     Razem:        real;       // (TODO: Razem = total time)
   end;
 
-  
+
+const
+  FFTSize = 512; // size of FFT data (output: FFTSize/2 values)
 type
-  TFFTData  = array[0..255] of Single;
+  TFFTData  = array[0..(FFTSize div 2-1)] of Single;
 
+type
   TPCMStereoSample = array[0..1] of Smallint;
   TPCMData  = array[0..511] of TPCMStereoSample;
 
diff --git a/Game/Code/Screens/UScreenOptionsRecord.pas b/Game/Code/Screens/UScreenOptionsRecord.pas
index 3da29f43..53d0b2ad 100644
--- a/Game/Code/Screens/UScreenOptionsRecord.pas
+++ b/Game/Code/Screens/UScreenOptionsRecord.pas
@@ -384,6 +384,7 @@ var
   DeviceCfg: PInputDeviceConfig;
   SelectSlide: TSelectSlide;
   ToneBoxWidth: real;
+  Volume: single;
 begin
   DrawBG;
   DrawFG;
@@ -434,22 +435,21 @@ begin
         glVertex2f(x2, y2);
         glVertex2f(x1, y2);
 
+        Volume := PreviewChannel[ChannelIndex].MaxSampleVolume();
+
         // coordinates for volume bar
         x1 := x1 + 1;
-        x2 := x1 + Trunc((SelectSlide.TextureSBG.W-4) *
-                    PreviewChannel[ChannelIndex].MaxSampleVolume()) + 1;
+        x2 := x1 + Trunc((SelectSlide.TextureSBG.W-4) * Volume) + 1;
         y1 := y1 + 1;
         y2 := y2 - 1;
 
         // draw volume bar
         glColor3f(RD, GD, BD);
         glVertex2f(x1, y1);
-        glColor3f(R, G, B);
-        glVertex2f(x2, y1);
+        glVertex2f(x1, y2);
         glColor3f(R, G, B);
         glVertex2f(x2, y2);
-        glColor3f(RD, GD, BD);
-        glVertex2f(x1, y2);
+        glVertex2f(x2, y1);
 
         //////////
         // draw Pitch
-- 
cgit v1.2.3