From f786f747554c79de3a7aa1fd63cd1e7c2e0763c3 Mon Sep 17 00:00:00 2001 From: tobigun 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