aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-03-03 03:16:57 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-03-03 03:16:57 +0000
commit6880109d9d38abfed6dececf863fdb7430fbb837 (patch)
tree4c04d9645071121185e69ebca50143b9b33cb46e
parenta022cbec1cb1245ab81bf008dd8835a860f38999 (diff)
downloadusdx-6880109d9d38abfed6dececf863fdb7430fbb837.tar.gz
usdx-6880109d9d38abfed6dececf863fdb7430fbb837.tar.xz
usdx-6880109d9d38abfed6dececf863fdb7430fbb837.zip
- Input now supports multiple SampleRates (not fixed to 44100Hz anymore)
- Input is not restricted to stereo input-devices anymore (mono and multi-channel devices work too) - Some improvements on the input-device detection - Retrieves native capture sample-rate on Vista and MacOSX with BASS (maybe this solves some problems with wrong sample-rates) - Capture-volume preview: a little modification to jay's approach. Now there are volume bars and pitch-displays. It needs some fine-tuning, maybe mog can do this if he likes. - Some indentation/clean-up/translations git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@900 b956fd51-792f-4845-bead-9b4dfca2ff2c
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas30
-rw-r--r--Game/Code/Classes/UAudioInput_Bass.pas77
-rw-r--r--Game/Code/Classes/UAudioInput_Portaudio.pas307
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas38
-rw-r--r--Game/Code/Classes/UDraw.pas2
-rw-r--r--Game/Code/Classes/UIni.pas448
-rw-r--r--Game/Code/Classes/ULog.pas7
-rw-r--r--Game/Code/Classes/UMain.pas624
-rw-r--r--Game/Code/Classes/URecord.pas393
-rw-r--r--Game/Code/Classes/UThemes.pas12
-rw-r--r--Game/Code/Screens/UScreenOptionsRecord.pas492
11 files changed, 1534 insertions, 896 deletions
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
index 1bb9208a..f4c22254 100644
--- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -4,7 +4,7 @@ unit UAudioDecoder_FFMpeg;
This unit is primarily based upon -
http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html
-
+
and tutorial03.c
http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
@@ -19,6 +19,7 @@ interface
{$I ../switches.inc}
+//{$DEFINE DebugFFMpegDecode}
uses
Classes,
@@ -39,7 +40,6 @@ uses
ULog,
UConfig;
-
type
PPacketQueue = ^TPacketQueue;
TPacketQueue = class
@@ -371,7 +371,9 @@ begin
if(url_feof(pbIOCtx) <> 0) then
begin
+ {$IFDEF DebugFFMpegDecode}
SafeWriteLn('feof');
+ {$ENDIF}
eofState := true;
continue;
end;
@@ -379,7 +381,9 @@ begin
// check for errors
if(url_ferror(pbIOCtx) = 0) then
begin
+ {$IFDEF DebugFFMpegDecode}
SafeWriteLn('Errorf');
+ {$ENDIF}
// no error -> wait for user input
SDL_Delay(100);
continue;
@@ -438,10 +442,12 @@ begin
if(len1 < 0) then
begin
- // if error, skip frame
+ // if error, skip frame
+ {$IFDEF DebugFFMpegDecode}
SafeWriteLn( 'Skip audio frame' );
+ {$ENDIF}
audio_pkt_size := 0;
- break;
+ break;
end;
Inc(audio_pkt_data, len1);
@@ -449,8 +455,8 @@ begin
if (data_size <= 0) then
begin
- // no data yet, get more frames
- continue;
+ // no data yet, get more frames
+ continue;
end;
// we have data, return it and come back for more later
@@ -475,7 +481,9 @@ begin
if (audio_pkt_data = PChar(FlushPacket.data)) then
begin
avcodec_flush_buffers(pCodecCtx);
+ {$IFDEF DebugFFMpegDecode}
SafeWriteLn('Flush');
+ {$ENDIF}
continue;
end;
@@ -484,7 +492,9 @@ begin
begin
// end-of-file reached -> set EOF-flag
SetEOF(true);
+ {$IFDEF DebugFFMpegDecode}
SafeWriteLn('EOF');
+ {$ENDIF}
// note: buffer is not (even partially) filled -> no data to return
exit;
end;
@@ -516,14 +526,14 @@ begin
if(audio_size < 0) then
begin
- // if error, output silence
+ // if error, output silence
audio_buf_size := 1024;
FillChar(audio_buf, audio_buf_size, #0);
//SafeWriteLn( 'Silence' );
end
else
begin
- audio_buf_size := audio_size;
+ audio_buf_size := audio_size;
end;
audio_buf_index := 0;
end;
@@ -619,7 +629,9 @@ begin
if (av_find_stream_info(pFormatCtx) < 0) then
exit;
+ {$IFDEF DebugFFMpegDecode}
dump_format(pFormatCtx, 0, pchar(Filename), 0);
+ {$ENDIF}
ffmpegStreamID := FindAudioStreamIndex(pFormatCtx);
if (ffmpegStreamID < 0) then
@@ -730,7 +742,7 @@ begin
begin
Self.firstPkt := pkt1.next;
if (Self.firstPkt = nil) then
- Self.lastPkt := nil;
+ Self.lastPkt := nil;
dec(Self.nbPackets);
//SafeWriteLn('Get: ' + inttostr(nbPackets));
diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas
index f99b0885..bf0c916e 100644
--- a/Game/Code/Classes/UAudioInput_Bass.pas
+++ b/Game/Code/Classes/UAudioInput_Bass.pas
@@ -39,7 +39,8 @@ type
BassDeviceID: integer; // DeviceID used by BASS
RecordStream: HSTREAM;
- procedure Start(); override;
+ function Init(): boolean;
+ function Start(): boolean; override;
procedure Stop(); override;
end;
@@ -68,36 +69,61 @@ end;
{ TBassInputDevice }
+function TBassInputDevice.Init(): boolean;
+begin
+ Result := false;
+
+ // TODO: Call once. Otherwise it's to slow
+ if not BASS_RecordInit(BassDeviceID) then
+ begin
+ Log.LogError('TBassInputDevice.Start: Error initializing device['+IntToStr(DeviceIndex)+']: ' +
+ TAudioCore_Bass.ErrorGetString());
+ Exit;
+ end;
+
+ Result := true;
+end;
+
{*
* Start input-capturing on this device.
* TODO: call BASS_RecordInit only once
*}
-procedure TBassInputDevice.Start();
+function TBassInputDevice.Start(): boolean;
+var
+ flags: Word;
const
- captureFreq = 44100;
+ latency = 20; // 20ms callback period (= latency)
begin
+ Result := false;
+
// recording already started -> stop first
if (RecordStream <> 0) then
Stop();
- // TODO: Call once. Otherwise it's to slow
- if not BASS_RecordInit(BassDeviceID) then
- begin
- Log.LogError('TBassInputDevice.Start: Error initializing device['+IntToStr(DeviceIndex)+']: ' +
- TAudioCore_Bass.ErrorGetString());
+ if not Init() then
Exit;
- end;
- SampleRate := captureFreq;
+ case AudioFormat.Format of
+ asfS16: flags := 0;
+ asfFloat: flags := BASS_SAMPLE_FLOAT;
+ asfU8: flags := BASS_SAMPLE_8BITS;
+ else begin
+ Log.LogError('Unhandled sample-format', 'TBassInputDevice.Start');
+ Exit;
+ end;
+ end;
- // capture in 44.1kHz/stereo/16bit and a 20ms callback period
- RecordStream := BASS_RecordStart(captureFreq, 2, MakeLong(0, 20),
+ // start capturing
+ RecordStream := BASS_RecordStart(AudioFormat.SampleRate, AudioFormat.Channels,
+ MakeLong(flags, latency),
@MicrophoneCallback, DeviceIndex);
if (RecordStream = 0) then
begin
BASS_RecordFree;
Exit;
end;
+
+ Result := true;
end;
{*
@@ -130,6 +156,7 @@ var
BassDevice: TBassInputDevice;
DeviceIndex: integer;
SourceIndex: integer;
+ RecordInfo: BASS_RECORDINFO;
begin
result := false;
@@ -162,9 +189,31 @@ begin
BassDevice.BassDeviceID := BassDeviceID;
BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
+ // retrieve recording device info
+ BASS_RecordGetInfo(RecordInfo);
+
+ // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
+
+ // check if BASS has capture-freq. info
+ if (RecordInfo.freq > 0) then
+ begin
+ // use current input sample rate (available only on Windows Vista and OSX).
+ // Recording at this rate will give the best quality and performance, as no resampling is required.
+ BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16)
+ end
+ else
+ begin
+ // BASS does not provide an explizit input channel count (except BASS_RECORDINFO.formats)
+ // but it doesn't fail if we use stereo input on a mono device
+ // -> use stereo by default
+ BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16)
+ end;
+
+ SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels);
+
// get input sources
SourceIndex := 0;
- BassDevice.MicInput := 0;
+ BassDevice.MicSource := 0;
// process each input
while true do
@@ -181,7 +230,7 @@ begin
Flags := BASS_RecordGetInput(SourceIndex);
if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then
begin
- BassDevice.MicInput := SourceIndex;
+ BassDevice.MicSource := SourceIndex;
end;
Inc(SourceIndex);
diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
index 665f1972..74c8d98f 100644
--- a/Game/Code/Classes/UAudioInput_Portaudio.pas
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -17,19 +17,18 @@ uses
implementation
uses
- URecord,
- UIni,
- ULog,
- UMain,
{$IFDEF UsePortmixer}
portmixer,
{$ENDIF}
- portaudio;
+ portaudio,
+ UAudioCore_Portaudio,
+ URecord,
+ UIni,
+ ULog,
+ UMain;
type
TAudioInput_Portaudio = class(TAudioInputBase)
- private
- function GetPreferredApiIndex(): TPaHostApiIndex;
public
function GetName: String; override;
function InitializeRecord: boolean; override;
@@ -41,7 +40,7 @@ type
RecordStream: PPaStream;
PaDeviceIndex: TPaDeviceIndex;
- procedure Start(); override;
+ function Start(): boolean; override;
procedure Stop(); override;
end;
@@ -49,88 +48,75 @@ function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longwor
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
inputDevice: Pointer): Integer; cdecl; forward;
+function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+ timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+ inputDevice: Pointer): Integer; cdecl; forward;
+
var
singleton_AudioInputPortaudio : IAudioInput;
-{* the default API used by Portaudio is the least common denominator
- * and might lack efficiency. ApiPreferenceOrder defines the order of
- * preferred APIs to use. The first API-type in the list is tried first. If it's
- * not available the next is tried, ...
- * If none of the preferred APIs was found the default API is used.
- * Pascal doesn't permit zero-length static arrays, so you can use paDefaultApi
- * as an array's only member if you do not have any preferences.
- * paDefaultApi also terminates a preferences list but this is optional.
- *}
-const
- paDefaultApi = -1;
-const
- ApiPreferenceOrder:
-{$IF Defined(WIN32)}
- // Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment
- // Note2: Windows Default-API is MME
- //array[0..0] of TPaHostApiTypeId = ( paDirectSound, paMME );
- array[0..0] of TPaHostApiTypeId = ( paDirectSound );
-{$ELSEIF Defined(LINUX)}
- // Note1: Portmixer has no mixer support for paJACK at the moment
- // Note2: Not tested, but ALSA might be better than OSS.
- array[0..1] of TPaHostApiTypeId = ( paALSA, paOSS );
-{$ELSEIF Defined(DARWIN)}
- // Note: Not tested.
- //array[0..0] of TPaHostApiTypeId = ( paCoreAudio );
- array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
-{$ELSE}
- array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
-{$IFEND}
-
{ TPortaudioInputDevice }
-procedure TPortaudioInputDevice.Start();
+function TPortaudioInputDevice.Start(): boolean;
var
Error: TPaError;
ErrorMsg: string;
inputParams: TPaStreamParameters;
deviceInfo: PPaDeviceInfo;
begin
+ Result := false;
+
// get input latency info
deviceInfo := Pa_GetDeviceInfo(PaDeviceIndex);
// set input stream parameters
- with inputParams do begin
+ with inputParams do
+ begin
device := PaDeviceIndex;
- channelCount := 2;
+ channelCount := AudioFormat.Channels;
sampleFormat := paInt16;
suggestedLatency := deviceInfo^.defaultLowInputLatency;
hostApiSpecificStreamInfo := nil;
end;
- Log.LogStatus(inttostr(PaDeviceIndex), 'Portaudio');
- Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio');
+ //Log.LogStatus(deviceInfo^.name, 'Portaudio');
+ //Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio');
// open input stream
- Error := Pa_OpenStream(RecordStream, @inputParams, nil, SampleRate,
+ Error := Pa_OpenStream(RecordStream, @inputParams, nil,
+ AudioFormat.SampleRate,
paFramesPerBufferUnspecified, paNoFlag,
@MicrophoneCallback, Pointer(Self));
- if(Error <> paNoError) then begin
+ if(Error <> paNoError) then
+ begin
ErrorMsg := Pa_GetErrorText(Error);
- Log.CriticalError('TPortaudioInputDevice.Start(): Error opening stream: ' + ErrorMsg);
- //Halt;
+ Log.LogError('Error opening stream: ' + ErrorMsg, 'TPortaudioInputDevice.Start');
+ Exit;
end;
// start capture
Error := Pa_StartStream(RecordStream);
- if(Error <> paNoError) then begin
+ if(Error <> paNoError) then
+ begin
Pa_CloseStream(RecordStream);
ErrorMsg := Pa_GetErrorText(Error);
- Log.CriticalError('TPortaudioInputDevice.Start(): Error starting stream: ' + ErrorMsg);
- //Halt;
+ Log.LogError('Error starting stream: ' + ErrorMsg, 'TPortaudioInputDevice.Start');
+ Exit;
end;
+
+ Result := true;
end;
procedure TPortaudioInputDevice.Stop();
begin
- if assigned(RecordStream) then begin
- Pa_StopStream(RecordStream);
+ if assigned(RecordStream) then
+ begin
+ // Note: do NOT call Pa_StopStream here!
+ // It gets stuck on devices with non-working callback as Pa_StopStream
+ // waits until all buffers have been handled (which never occurs in that
+ // case).
+ // Pa_CloseStream internally calls Pa_AbortStream which works as expected.
Pa_CloseStream(RecordStream);
end;
end;
@@ -143,151 +129,204 @@ begin
result := 'Portaudio';
end;
-function TAudioInput_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
-var
- i: integer;
-begin
- result := -1;
-
- // select preferred sound-API
- for i:= 0 to High(ApiPreferenceOrder) do
- begin
- if(ApiPreferenceOrder[i] <> paDefaultApi) then begin
- // check if API is available
- result := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]);
- if(result >= 0) then
- break;
- end;
- end;
-
- // None of the preferred APIs is available -> use default
- if(result < 0) then begin
- result := Pa_GetDefaultHostApi();
- end;
-end;
-
function TAudioInput_Portaudio.InitializeRecord(): boolean;
var
i: integer;
- apiIndex: TPaHostApiIndex;
- apiInfo: PPaHostApiInfo;
+ paApiIndex: TPaHostApiIndex;
+ paApiInfo: PPaHostApiInfo;
deviceName: string;
deviceIndex: TPaDeviceIndex;
deviceInfo: PPaDeviceInfo;
sourceCnt: integer;
+ sourceIndex: integer;
sourceName: string;
+ channelCnt: integer;
SC: integer; // soundcard
- SCI: integer; // soundcard source
err: TPaError;
errMsg: string;
paDevice: TPortaudioInputDevice;
inputParams: TPaStreamParameters;
stream: PPaStream;
+ streamInfo: PPaStreamInfo;
+ sampleRate: integer;
{$IFDEF UsePortmixer}
mixer: PPxMixer;
{$ENDIF}
-const
- captureFreq = 44100;
+ cbPolls: integer;
+ cbWorks: boolean;
begin
result := false;
+ // initialize portaudio
err := Pa_Initialize();
- if(err <> paNoError) then begin
- Log.LogError('Portaudio.InitializeRecord: ' + Pa_GetErrorText(err));
+ if(err <> paNoError) then
+ begin
+ Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
Exit;
end;
- apiIndex := GetPreferredApiIndex();
- apiInfo := Pa_GetHostApiInfo(apiIndex);
+
+ // choose the best available Audio-API
+ paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
+ if(paApiIndex = -1) then
+ begin
+ Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.InitializeRecord');
+ Exit;
+ end;
+
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
SC := 0;
// init array-size to max. input-devices count
- SetLength(AudioInputProcessor.Device, apiInfo^.deviceCount);
+ SetLength(AudioInputProcessor.Device, paApiInfo^.deviceCount);
for i:= 0 to High(AudioInputProcessor.Device) do
begin
// convert API-specific device-index to global index
- deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(apiIndex, i);
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+ channelCnt := deviceInfo^.maxInputChannels;
+
// current device is no input device -> skip
- if(deviceInfo^.maxInputChannels <= 0) then
+ if (channelCnt <= 0) then
continue;
+ // portaudio returns a channel-count of 128 for some devices
+ // (e.g. the "default"-device), so we have to detect those
+ // fantasy channel counts.
+ if (channelCnt > 8) then
+ channelCnt := 2;
+
paDevice := TPortaudioInputDevice.Create();
AudioInputProcessor.Device[SC] := paDevice;
-
+
// retrieve device-name
deviceName := deviceInfo^.name;
paDevice.Description := deviceName;
paDevice.PaDeviceIndex := deviceIndex;
+ if (deviceInfo^.defaultSampleRate > 0) then
+ sampleRate := Trunc(deviceInfo^.defaultSampleRate)
+ else
+ sampleRate := 44100;
+
// setup desired input parameters
- with inputParams do begin
+ with inputParams do
+ begin
device := deviceIndex;
- channelCount := 2;
+ channelCount := channelCnt;
sampleFormat := paInt16;
suggestedLatency := deviceInfo^.defaultLowInputLatency;
hostApiSpecificStreamInfo := nil;
end;
- paDevice.SampleRate := captureFreq;
-
// check if device supports our input-format
- err := Pa_IsFormatSupported(@inputParams, nil, paDevice.SampleRate);
- if(err <> 0) then begin
+ // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+ // not be set correctly in OSS)
+ err := Pa_IsFormatSupported(@inputParams, nil, sampleRate);
+ if(err <> 0) then
+ begin
// format not supported -> skip
errMsg := Pa_GetErrorText(err);
- Log.LogError('Portaudio.InitializeRecord, device: "'+ deviceName +'" '
- + '('+ errMsg +')');
+ Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')',
+ 'TAudioInput_Portaudio.InitializeRecord');
paDevice.Free();
continue;
end;
- // TODO: retry with mono if stereo is not supported
- // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
- // not be set correctly in OSS)
- // use TPaDeviceInfo.defaultSampleRate
-
- err := Pa_OpenStream(stream, @inputParams, nil, paDevice.SampleRate,
- paFramesPerBufferUnspecified, paNoFlag, @MicrophoneCallback, nil);
- if(err <> paNoError) then begin
+ // check if the device really works
+ err := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
+ paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil);
+ if(err <> paNoError) then
+ begin
// unable to open device -> skip
errMsg := Pa_GetErrorText(err);
- Log.LogError('Portaudio.InitializeRecord, device: "'+ deviceName +'" '
- + '('+ errMsg +')');
+ Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')',
+ 'TAudioInput_Portaudio.InitializeRecord');
paDevice.Free();
continue;
end;
- {$IFDEF UsePortmixer}
-
- // use default mixer
- mixer := Px_OpenMixer(stream, 0);
+ // check if mic-callback works (might not be called on some devices)
- // get input count
- sourceCnt := Px_GetNumInputSources(mixer);
- SetLength(paDevice.Source, sourceCnt);
-
- // get input names
- for SCI := 0 to sourceCnt-1 do
+ // start the callback
+ Pa_StartStream(stream);
+
+ cbWorks := false;
+ // check if the callback was called (poll for max. 200ms)
+ for cbPolls := 1 to 20 do
begin
- sourceName := Px_GetInputSourceName(mixer, SCI);
- paDevice.Source[SCI].Name := sourceName;
+ // if the test-callback was called it should be aborted now
+ if (Pa_IsStreamActive(stream) = 0) then
+ begin
+ cbWorks := true;
+ break;
+ end;
+ // not yet aborted, wait and try (poll) again
+ Pa_Sleep(10);
end;
- Px_CloseMixer(mixer);
-
- {$ELSE} // !UsePortmixer
+ // finally abort the stream (Note: Pa_StopStream might hang here)
+ Pa_AbortStream(stream);
- //Pa_StartStream(stream);
- // TODO: check if callback was called (this problem may occur on some devices)
- //Pa_StopStream(stream);
+ // ignore device if callback did not work
+ if (not cbWorks) then
+ begin
+ Log.LogError('Device "'+paDevice.Description+'" does not respond',
+ 'TAudioInput_Portaudio.InitializeRecord');
+ Pa_CloseStream(stream);
+ paDevice.Free();
+ continue;
+ end;
- // create a standard input source
- SetLength(paDevice.Source, 1);
- paDevice.Source[0].Name := 'Standard';
+ // adjust sample-rate (might be changed by portaudio)
+ streamInfo := Pa_GetStreamInfo(stream);
+ if (streamInfo <> nil) then
+ begin
+ if (sampleRate <> Trunc(streamInfo^.sampleRate)) then
+ begin
+ Log.LogStatus('Portaudio changed Samplerate from ' + IntToStr(sampleRate) +
+ ' to ' + FloatToStr(streamInfo^.sampleRate),
+ 'TAudioInput_Portaudio.InitializeRecord');
+ sampleRate := Trunc(streamInfo^.sampleRate);
+ end;
+ end;
+
+ // create audio-format info and resize capture-buffer array
+ paDevice.AudioFormat := TAudioFormatInfo.Create(
+ channelCnt,
+ sampleRate,
+ asfS16
+ );
+ SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
+
+ Log.LogStatus('InputDevice "'+paDevice.Description+'"@' +
+ IntToStr(paDevice.AudioFormat.Channels)+'x'+
+ IntToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
+ FloatTostr(inputParams.suggestedLatency)+'sec)' ,
+ 'Portaudio.InitializeRecord');
+ {$IFDEF UsePortmixer}
+ // use default mixer
+ mixer := Px_OpenMixer(stream, 0);
+
+ // get input count
+ sourceCnt := Px_GetNumInputSources(mixer);
+ SetLength(paDevice.Source, sourceCnt);
+
+ // get input names
+ for sourceIndex := 0 to sourceCnt-1 do
+ begin
+ sourceName := Px_GetInputSourceName(mixer, sourceIndex);
+ paDevice.Source[sourceIndex].Name := sourceName;
+ end;
+
+ Px_CloseMixer(mixer);
+ {$ELSE} // not UsePortmixer
+ // create a standard input source
+ SetLength(paDevice.Source, 1);
+ paDevice.Source[0].Name := 'Standard';
{$ENDIF}
// close test-stream
@@ -304,9 +343,6 @@ begin
Log.LogStatus('#Soundcards: ' + inttostr(SC), 'Portaudio');
- {
- SoundCard[SC].InputSelected := Mic[Device];
- }
result := true;
end;
@@ -336,6 +372,17 @@ begin
result := paContinue;
end;
+{*
+ * Portaudio test capture callback.
+ *}
+function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+ timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+ inputDevice: Pointer): Integer; cdecl;
+begin
+ // this callback is called only once
+ result := paAbort;
+end;
+
initialization
singleton_AudioInputPortaudio := TAudioInput_Portaudio.create();
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
index 96fff957..0f4eb7ae 100644
--- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -18,6 +18,7 @@ implementation
uses
portaudio,
+ UAudioCore_Portaudio,
UAudioPlayback_SoftMixer,
ULog,
UIni,
@@ -38,7 +39,7 @@ type
var
singleton_AudioPlaybackPortaudio : IAudioPlayback;
-
+
{ TAudioPlayback_Portaudio }
function PortaudioAudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
@@ -59,44 +60,43 @@ end;
function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
var
- paApi : TPaHostApiIndex;
+ paApiIndex : TPaHostApiIndex;
paApiInfo : PPaHostApiInfo;
paOutParams : TPaStreamParameters;
paOutDevice : TPaDeviceIndex;
paOutDeviceInfo : PPaDeviceInfo;
err : TPaError;
-const
- sampleFreq = 44100;
+ sampleRate : integer;
begin
result := false;
Pa_Initialize();
- // FIXME: determine automatically
- {$IFDEF WIN32}
- paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
- {$ELSE}
- paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA);
- {$ENDIF}
- if (paApi < 0) then
- begin
- Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio');
- exit;
- end;
+ paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
+ if(paApiIndex = -1) then
+ begin
+ Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
+ Exit;
+ end;
- paApiInfo := Pa_GetHostApiInfo(paApi);
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
paOutDevice := paApiInfo^.defaultOutputDevice;
paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
+ if (paOutDeviceInfo^.defaultSampleRate > 0) then
+ sampleRate := Trunc(paOutDeviceInfo^.defaultSampleRate)
+ else
+ sampleRate := 44100;
+
with paOutParams do begin
device := paOutDevice;
channelCount := 2;
sampleFormat := paInt16;
- suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency;
+ suggestedLatency := paOutDeviceInfo^.defaultLowOutputLatency;
hostApiSpecificStreamInfo := nil;
end;
- err := Pa_OpenStream(paStream, nil, @paOutParams, sampleFreq,
+ err := Pa_OpenStream(paStream, nil, @paOutParams, sampleRate,
paFramesPerBufferUnspecified,
paNoFlag, @PortaudioAudioCallback, Self);
if(err <> paNoError) then begin
@@ -106,7 +106,7 @@ begin
FormatInfo := TAudioFormatInfo.Create(
paOutParams.channelCount,
- sampleFreq,
+ sampleRate,
asfS16 // FIXME: is paInt16 system-dependant or -independant?
);
diff --git a/Game/Code/Classes/UDraw.pas b/Game/Code/Classes/UDraw.pas
index dc2f4dce..479226b3 100644
--- a/Game/Code/Classes/UDraw.pas
+++ b/Game/Code/Classes/UDraw.pas
@@ -158,7 +158,7 @@ end;
procedure SingDrawOscilloscope(X, Y, W, H: real; NrSound: integer);
var
SampleIndex: integer;
- Sound: TSound;
+ Sound: TCaptureBuffer;
MaxX, MaxY: real;
begin;
Sound := AudioInputProcessor.Sound[NrSound];
diff --git a/Game/Code/Classes/UIni.pas b/Game/Code/Classes/UIni.pas
index 4de18ba3..232021b6 100644
--- a/Game/Code/Classes/UIni.pas
+++ b/Game/Code/Classes/UIni.pas
@@ -8,94 +8,104 @@ interface
{$I switches.inc}
-uses IniFiles, ULog, SysUtils;
+uses
+ Classes,
+ IniFiles,
+ ULog,
+ SysUtils;
type
PInputDeviceConfig = ^TInputDeviceConfig;
TInputDeviceConfig = record
Name: string;
Input: integer;
- ChannelToPlayerMap: array[0..1] of integer;
+ ChannelToPlayerMap: array of integer;
end;
type
TIni = class
- Name: array[0..11] of string;
-
- // Templates for Names Mod
- NameTeam: array[0..2] of string;
- NameTemplate: array[0..11] of string;
-
- //Filename of the opened iniFile
- Filename: string;
-
- // Game
- Players: integer;
- Difficulty: integer;
- Language: integer;
- Tabs: integer;
- Tabs_at_startup:integer; //Tabs at Startup fix
- Sorting: integer;
- Debug: integer;
-
- // Graphics
- Screens: integer;
- Resolution: integer;
- Depth: integer;
- FullScreen: integer;
- TextureSize: integer;
- SingWindow: integer;
- Oscilloscope: integer;
- Spectrum: integer;
- Spectrograph: integer;
- MovieSize: integer;
-
- // Sound
- MicBoost: integer;
- ClickAssist: integer;
- BeatClick: integer;
- SavePlayback: integer;
- Threshold: integer;
-
- //Song Preview
- PreviewVolume: integer;
- PreviewFading: integer;
-
- // Lyrics
- LyricsFont: integer;
- LyricsEffect: integer;
- Solmization: integer;
-
- // Themes
- Theme: integer;
- SkinNo: integer;
- Color: integer;
-
- // Record
- InputDeviceConfig: array of TInputDeviceConfig;
-
- // Advanced
- LoadAnimation: integer;
- EffectSing: integer;
- ScreenFade: integer;
- AskbeforeDel: integer;
- OnSongClick: integer;
- LineBonus: integer;
- PartyPopup: integer;
-
- // Controller
- Joypad: integer;
-
- // Soundcards
- SoundCard: array[0..7, 1..2] of integer;
-
- // Devices
- LPT: integer;
-
- procedure Load;
- procedure Save;
- procedure SaveNames;
- procedure SaveLevel;
+ private
+ function ExtractKeyIndex(const key: string; const prefix: string; suffix: string): integer;
+ function GetMaxKeyIndex(keys: TStringList; const prefix: string; const suffix: string): integer;
+ procedure LoadInputDeviceCfg(IniFile: TMemIniFile);
+ procedure SaveInputDeviceCfg(IniFile: TIniFile);
+ public
+ Name: array[0..11] of string;
+
+ // Templates for Names Mod
+ NameTeam: array[0..2] of string;
+ NameTemplate: array[0..11] of string;
+
+ //Filename of the opened iniFile
+ Filename: string;
+
+ // Game
+ Players: integer;
+ Difficulty: integer;
+ Language: integer;
+ Tabs: integer;
+ Tabs_at_startup:integer; //Tabs at Startup fix
+ Sorting: integer;
+ Debug: integer;
+
+ // Graphics
+ Screens: integer;
+ Resolution: integer;
+ Depth: integer;
+ FullScreen: integer;
+ TextureSize: integer;
+ SingWindow: integer;
+ Oscilloscope: integer;
+ Spectrum: integer;
+ Spectrograph: integer;
+ MovieSize: integer;
+
+ // Sound
+ MicBoost: integer;
+ ClickAssist: integer;
+ BeatClick: integer;
+ SavePlayback: integer;
+ Threshold: integer;
+
+ //Song Preview
+ PreviewVolume: integer;
+ PreviewFading: integer;
+
+ // Lyrics
+ LyricsFont: integer;
+ LyricsEffect: integer;
+ Solmization: integer;
+
+ // Themes
+ Theme: integer;
+ SkinNo: integer;
+ Color: integer;
+
+ // Record
+ InputDeviceConfig: array of TInputDeviceConfig;
+
+ // Advanced
+ LoadAnimation: integer;
+ EffectSing: integer;
+ ScreenFade: integer;
+ AskbeforeDel: integer;
+ OnSongClick: integer;
+ LineBonus: integer;
+ PartyPopup: integer;
+
+ // Controller
+ Joypad: integer;
+
+ // Soundcards
+ SoundCard: array[0..7, 1..2] of integer;
+
+ // Devices
+ LPT: integer;
+
+ procedure Load();
+ procedure Save();
+ procedure SaveNames;
+ procedure SaveLevel;
end;
@@ -105,8 +115,6 @@ var
ILanguage: array of string;
ITheme: array of string;
ISkin: array of string;
- ICard: array of string;
- IInput: array of string;
const
IPlayers: array[0..4] of string = ('1', '2', '3', '4', '6');
@@ -171,16 +179,199 @@ const
implementation
-uses //UFiles,
- UMain,
- SDL,
- ULanguage,
- UPlatform,
- USkins,
- URecord,
- UCommandLine;
+uses
+ StrUtils,
+ //UFiles,
+ UMain,
+ SDL,
+ ULanguage,
+ UPlatform,
+ USkins,
+ URecord,
+ UCommandLine;
+
+function TIni.ExtractKeyIndex(const key: string; const prefix: string; suffix: string): integer;
+var
+ tmpStr: string;
+begin
+ Result := -1;
+ if not AnsiStartsText(prefix, key) then
+ Exit;
+ tmpStr := AnsiReplaceText(key, prefix, '');
+ tmpStr := AnsiReplaceText(tmpStr, suffix, '');
+ Result := StrToIntDef(tmpStr, -1);
+end;
+
+function TIni.GetMaxKeyIndex(keys: TStringList; const prefix: string; const suffix: string): integer;
+var
+ i: integer;
+ keyIndex: integer;
+begin
+ Result := -1;
+ for i := 0 to keys.Count-1 do
+ begin
+ keyIndex := ExtractKeyIndex(keys[i], prefix, suffix);
+ if (keyIndex > Result) then
+ Result := keyIndex;
+ end;
+end;
+
+procedure TIni.LoadInputDeviceCfg(IniFile: TMemIniFile);
+var
+ deviceIndex: integer;
+ deviceCfgIndex: integer;
+ deviceCfg: PInputDeviceConfig;
+ device: TAudioInputDevice;
+ deviceIniIndex: integer;
+ deviceIniStr: string;
+ channelCount: integer;
+ channelIndex: integer;
+ channelIndexStr: string;
+ newDevice: boolean;
+ recordKeys: TStringList;
+ i: integer;
+begin
+ recordKeys := TStringList.Create();
+
+ // read all record-keys for filtering
+ IniFile.ReadSection('Record', recordKeys);
+
+ deviceCfgIndex := 0;
+ SetLength(InputDeviceConfig, 0);
+
+ for i := 0 to recordKeys.Count-1 do
+ begin
+ // find next device-name
+ deviceIniIndex := ExtractKeyIndex(recordKeys[i], 'DeviceName[', ']');
+ if (deviceIniIndex < 0) then
+ continue;
+ deviceIniStr := IntToStr(deviceIniIndex);
+
+ if not IniFile.ValueExists('Record', 'DeviceName['+deviceIniStr+']') then
+ break;
+
+ // resize list
+ SetLength(InputDeviceConfig, deviceCfgIndex+1);
+
+ deviceCfg := @InputDeviceConfig[deviceCfgIndex];
+
+ // read an input device's config.
+ // Note: All devices are appended to the list whether they exist or not.
+ // Otherwise an external device's config will be lost if it is not
+ // connected (e.g. singstar mics or USB-Audio devices).
+ deviceCfg.Name := IniFile.ReadString('Record', 'DeviceName['+deviceIniStr+']', '');
+ deviceCfg.Input := IniFile.ReadInteger('Record', 'Input['+deviceIniStr+']', 0);
+
+ // find the largest channel-number of the current device in the ini-file
+ channelCount := GetMaxKeyIndex(recordKeys, 'Channel(', ')['+deviceIniStr+']');
+ if (channelCount < 0) then
+ channelCount := 0;
+
+ SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
+
+ // read channel-to-player mapping for every channel of the current device
+ // or set non-configured channels to no player (=0).
+ for channelIndex := 0 to High(deviceCfg.ChannelToPlayerMap) do
+ begin
+ channelIndexStr := IntToStr(channelIndex+1);
+ deviceCfg.ChannelToPlayerMap[channelIndex] :=
+ IniFile.ReadInteger('Record', 'Channel('+channelIndexStr+')['+deviceIniStr+']', 0);
+ end;
+
+ Inc(deviceCfgIndex);
+ end;
+
+ recordKeys.Free();
+
+ // Input devices - append detected soundcards
+ for deviceIndex := 0 to High(AudioInputProcessor.Device) do
+ begin
+ newDevice := true;
+ for deviceCfgIndex := 0 to High(InputDeviceConfig) do
+ begin //Search for Card in List
+ deviceCfg := @InputDeviceConfig[deviceCfgIndex];
+ device := AudioInputProcessor.Device[deviceIndex];
+
+ if (deviceCfg.Name = Trim(device.Description)) then
+ begin
+ newDevice := false;
+
+ // store highest channel index as an offset for the new channels
+ channelIndex := High(deviceCfg.ChannelToPlayerMap);
+ // add missing channels or remove non-existing ones
+ SetLength(deviceCfg.ChannelToPlayerMap, device.AudioFormat.Channels);
+ // initialize added channels to 0
+ for i := channelIndex+1 to High(deviceCfg.ChannelToPlayerMap) do
+ begin
+ deviceCfg.ChannelToPlayerMap[i] := 0;
+ end;
+
+ // associate ini-index with device
+ device.CfgIndex := deviceCfgIndex;
+
+ Break;
+ end;
+ end;
+
+ //If not in List -> Add
+ if newDevice then
+ begin
+ // resize list
+ SetLength(InputDeviceConfig, Length(InputDeviceConfig)+1);
+ deviceCfgIndex := High(InputDeviceConfig);
+
+ deviceCfg := @InputDeviceConfig[deviceCfgIndex];
+ device := AudioInputProcessor.Device[deviceIndex];
+
+ deviceCfg.Name := Trim(AudioInputProcessor.Device[deviceIndex].Description);
+ deviceCfg.Input := 0;
+
+ channelCount := device.AudioFormat.Channels;
+ SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
+
+ for channelIndex := 0 to channelCount-1 do
+ begin
+ // set default at first start of USDX (1st device, 1st channel -> player1)
+ if ((channelIndex = 0) and (deviceCfgIndex = 0)) then
+ deviceCfg.ChannelToPlayerMap[0] := 1
+ else
+ deviceCfg.ChannelToPlayerMap[channelIndex] := 0;
+ end;
+
+ // associate ini-index with device
+ device.CfgIndex := deviceCfgIndex;
+ end;
+ end;
+
+end;
+
+procedure TIni.SaveInputDeviceCfg(IniFile: TIniFile);
+var
+ deviceIndex: integer;
+ deviceIndexStr: string;
+ channelIndex: integer;
+ channelIndexStr: string;
+ valueStr: string;
+begin
+ for deviceIndex := 0 to High(InputDeviceConfig) do begin
+ deviceIndexStr := IntToStr(deviceIndex+1);
+
+ valueStr := InputDeviceConfig[deviceIndex].Name;
+ IniFile.WriteString('Record', 'DeviceName['+deviceIndexStr+']', valueStr);
+
+ valueStr := IntToStr(InputDeviceConfig[deviceIndex].Input);
+ IniFile.WriteString('Record', 'Input['+deviceIndexStr+']', valueStr);
+
+ for channelIndex := 0 to High(InputDeviceConfig[deviceIndex].ChannelToPlayerMap) do
+ begin
+ channelIndexStr := IntToStr(channelIndex+1);
+ valueStr := IntToStr(InputDeviceConfig[deviceIndex].ChannelToPlayerMap[channelIndex]);
+ IniFile.WriteString('Record', 'Channel('+channelIndexStr+')['+deviceIndexStr+']', valueStr);
+ end;
+ end;
+end;
-procedure TIni.Load;
+procedure TIni.Load();
var
IniFile: TMemIniFile;
ThemeIni: TMemIniFile;
@@ -386,7 +577,7 @@ begin
if Tekst = ISavePlayback[Pet] then Ini.SavePlayback := Pet;
// Threshold
- Tekst := IniFile.ReadString('Sound', 'Threshold', IThreshold[2]);
+ Tekst := IniFile.ReadString('Sound', 'Threshold', IThreshold[1]);
for Pet := 0 to High(IThreshold) do
if Tekst = IThreshold[Pet] then Ini.Threshold := Pet;
@@ -479,65 +670,7 @@ begin
for Pet := 0 to High(IColor) do
if Tekst = IColor[Pet] then Ini.Color := Pet;
- // Input devices - load ini list
- SetLength(InputDeviceConfig, 0);
- I := 1;
- while (IniFile.ValueExists('Record', 'DeviceName'+IntToStr(I))) do begin
- // resize list
- SetLength(InputDeviceConfig, Length(InputDeviceConfig)+1);
- I2 := High(InputDeviceConfig);
-
- // read an input device's config.
- // Note: All devices are appended to the list whether they exist or not.
- // Otherwise an external device's config will be lost if it is not
- // connected (e.g. singstar mics or USB-Audio devices).
- InputDeviceConfig[I2].Name :=
- IniFile.ReadString('Record', 'DeviceName'+IntToStr(I), '');
- InputDeviceConfig[I2].Input :=
- IniFile.ReadInteger('Record', 'Input'+IntToStr(I), 0);
- InputDeviceConfig[I2].ChannelToPlayerMap[0] :=
- IniFile.ReadInteger('Record', 'ChannelL'+IntToStr(I), 0);
- InputDeviceConfig[I2].ChannelToPlayerMap[1] :=
- IniFile.ReadInteger('Record', 'ChannelR'+IntToStr(I), 0);
-
- Inc(I);
- end;
-
- // Input devices - append detected soundcards
- for I := 0 to High(AudioInputProcessor.Device) do
- begin
- B := False;
- For I2 := 0 to High(InputDeviceConfig) do
- begin //Search for Card in List
- if (InputDeviceConfig[I2].Name = Trim(AudioInputProcessor.Device[I].Description)) then
- begin
- B := True;
- // associate ini-index with device
- AudioInputProcessor.Device[I].CfgIndex := I2;
- Break;
- end;
- end;
-
- //If not in List -> Add
- If not B then
- begin
- // resize list
- SetLength(InputDeviceConfig, Length(InputDeviceConfig)+1);
- I2 := High(InputDeviceConfig);
-
- InputDeviceConfig[I2].Name := Trim(AudioInputProcessor.Device[I].Description);
- InputDeviceConfig[I2].Input := 0;
- InputDeviceConfig[I2].ChannelToPlayerMap[0] := 0;
- InputDeviceConfig[I2].ChannelToPlayerMap[1] := 0;
-
- // associate ini-index with device
- AudioInputProcessor.Device[I].CfgIndex := I2;
-
- // set default at first start of USDX (1st device, 1st channel -> player1)
- if (I2 = 0) then
- InputDeviceConfig[I2].ChannelToPlayerMap[0] := 1;
- end;
- end;
+ LoadInputDeviceCfg(IniFile);
//Advanced Settings
@@ -725,22 +858,7 @@ begin
Tekst := IColor[Ini.Color];
IniFile.WriteString('Themes', 'Color', Tekst);
- // Record
- for I := 0 to High(InputDeviceConfig) do begin
- S := IntToStr(I+1);
-
- Tekst := InputDeviceConfig[I].Name;
- IniFile.WriteString('Record', 'DeviceName' + S, Tekst);
-
- Tekst := IntToStr(InputDeviceConfig[I].Input);
- IniFile.WriteString('Record', 'Input' + S, Tekst);
-
- Tekst := IntToStr(InputDeviceConfig[I].ChannelToPlayerMap[0]);
- IniFile.WriteString('Record', 'ChannelL' + S, Tekst);
-
- Tekst := IntToStr(InputDeviceConfig[I].ChannelToPlayerMap[1]);
- IniFile.WriteString('Record', 'ChannelR' + S, Tekst);
- end;
+ SaveInputDeviceCfg(IniFile);
//Log.LogError(InttoStr(Length(CardList)) + ' Cards Saved');
diff --git a/Game/Code/Classes/ULog.pas b/Game/Code/Classes/ULog.pas
index 542fa0b3..15c590ae 100644
--- a/Game/Code/Classes/ULog.pas
+++ b/Game/Code/Classes/ULog.pas
@@ -286,7 +286,6 @@ var
FS: TFileStream;
FileName: string;
Num: integer;
- BL: integer;
begin
for Num := 1 to 9999 do begin
FileName := IntToStr(Num);
@@ -298,10 +297,8 @@ begin
FS := TFileStream.Create(FileName, fmCreate);
- for BL := 0 to High(AudioInputProcessor.Sound[SoundNr].BufferLong) do begin
- AudioInputProcessor.Sound[SoundNr].BufferLong[BL].Seek(0, soBeginning);
- FS.CopyFrom(AudioInputProcessor.Sound[SoundNr].BufferLong[BL], AudioInputProcessor.Sound[SoundNr].BufferLong[BL].Size);
- end;
+ AudioInputProcessor.Sound[SoundNr].BufferLong.Seek(0, soBeginning);
+ FS.CopyFrom(AudioInputProcessor.Sound[SoundNr].BufferLong, AudioInputProcessor.Sound[SoundNr].BufferLong.Size);
FS.Free;
end;
diff --git a/Game/Code/Classes/UMain.pas b/Game/Code/Classes/UMain.pas
index ddcad686..66c8efe9 100644
--- a/Game/Code/Classes/UMain.pas
+++ b/Game/Code/Classes/UMain.pas
@@ -9,35 +9,35 @@ interface
{$I switches.inc}
uses
- SDL,
- UGraphic,
- UMusic,
- URecord,
- UTime,
- SysUtils,
- UDisplay,
- UIni,
- ULog,
- ULyrics,
- UScreenSing,
- USong,
- OpenGL12,
- {$IFDEF UseSerialPort}
- zlportio {you can disable it and all PortWriteB calls},
- {$ENDIF}
- ULCD,
- ULight,
- UThemes;
+ SDL,
+ UGraphic,
+ UMusic,
+ URecord,
+ UTime,
+ SysUtils,
+ UDisplay,
+ UIni,
+ ULog,
+ ULyrics,
+ UScreenSing,
+ USong,
+ OpenGL12,
+ {$IFDEF UseSerialPort}
+ zlportio, //you can disable it and all PortWriteB calls
+ {$ENDIF}
+ ULCD,
+ ULight,
+ UThemes;
type
TPlayer = record
Name: string;
- //Index in Teaminfo record
+ // Index in Teaminfo record
TeamID: Byte;
PlayerID: Byte;
- //Scores
+ // Scores
Score: real;
ScoreLine: real;
ScoreGolden: real;
@@ -47,41 +47,31 @@ type
ScoreGoldenI: integer;
ScoreTotalI: integer;
-
-
- //LineBonus Mod
+ // LineBonus Mod
ScoreLast: Real;//Last Line Score
- //PerfectLineTwinkle Mod (effect)
+ // PerfectLineTwinkle Mod (effect)
LastSentencePerfect: Boolean;
- //PerfectLineTwinkle Mod end
-
-// Meter: real;
+ //Meter: real;
HighNut: integer;
IlNut: integer;
Nuta: array of record
Start: integer;
Dlugosc: integer;
- Detekt: real; // dokladne miejsce, w ktorym wykryto ta nute
+ Detekt: real; // accurate place, detected in the note
Ton: real;
- Perfect: boolean; // true if the note matches the original one, lit the star
-
-
+ Perfect: boolean; // true if the note matches the original one, lit the star
// Half size Notes Patch
Hit: boolean; // true if the note Hits the Line
- //end Half size Notes Patch
-
-
-
end;
end;
var
- //Absolute Paths
+ // Absolute Paths
GamePath: string;
SoundPath: string;
SongPath: string;
@@ -105,7 +95,7 @@ var
FileName: string;
Restart: boolean;
- // gracz i jego nuty
+ // player and music info
Player: array of TPlayer;
PlayersPlay: integer;
@@ -129,23 +119,32 @@ procedure ClearScores(PlayerNum: integer);
implementation
-uses USongs,
- UJoystick,
- math,
- UCommandLine, ULanguage, SDL_ttf,
- USkins, UCovers, UCatCovers, UDataBase, UPlaylist, UDLLManager,
- UParty, UCore, UGraphicClasses, UPluginDefs, UPlatform;
-
-const
- Version = 'UltraStar Deluxe V 1.10 Alpha Build';
-
-Procedure Main;
+uses
+ USongs,
+ UJoystick,
+ math,
+ UCommandLine,
+ ULanguage,
+ SDL_ttf,
+ USkins,
+ UCovers,
+ UCatCovers,
+ UDataBase,
+ UPlaylist,
+ UDLLManager,
+ UParty,
+ UConfig,
+ UCore,
+ UGraphicClasses,
+ UPluginDefs,
+ UPlatform;
+
+procedure Main;
var
WndTitle: string;
begin
try
-
- WndTitle := Version;
+ WndTitle := USDXVersionStr;
if Platform.TerminateIfAlreadyRunning( {var} WndTitle) then
Exit;
@@ -161,7 +160,7 @@ begin
// Log + Benchmark
Log := TLog.Create;
Log.Title := WndTitle;
- Log.Enabled := Not Params.NoLog;
+ Log.Enabled := not Params.NoLog;
Log.BenchmarkStart(0);
// Language
@@ -170,8 +169,8 @@ begin
InitializePaths;
Log.LogStatus('Load Language', 'Initialization');
Language := TLanguage.Create;
- //Add Const Values:
- Language.AddConst('US_VERSION', Version);
+ // Add Const Values:
+ Language.AddConst('US_VERSION', USDXVersionStr);
Log.BenchmarkEnd(1);
Log.LogBenchmark('Loading Language', 1);
@@ -185,7 +184,7 @@ begin
// SDL_ttf
Log.BenchmarkStart(1);
Log.LogStatus('Initialize SDL_ttf', 'Initialization');
- TTF_Init(); //ttf_quit();
+ TTF_Init();
Log.BenchmarkEnd(1);
Log.LogBenchmark('Initializing SDL_ttf', 1);
@@ -223,8 +222,9 @@ begin
Log.BenchmarkStart(1);
Log.LogStatus('Load LCD', 'Initialization');
LCD := TLCD.Create;
- if Ini.LPT = 1 then begin
- // LCD.HalfInterface := true;
+ if Ini.LPT = 1 then
+ begin
+ //LCD.HalfInterface := true;
LCD.Enable;
LCD.Clear;
LCD.WriteText(1, ' UltraStar ');
@@ -237,7 +237,8 @@ begin
Log.BenchmarkStart(1);
Log.LogStatus('Load Light', 'Initialization');
Light := TLight.Create;
- if Ini.LPT = 2 then begin
+ if Ini.LPT = 2 then
+ begin
Light.Enable;
end;
Log.BenchmarkEnd(1);
@@ -270,7 +271,7 @@ begin
//Log.BenchmarkStart(1);
Log.LogStatus('Creating Song Array', 'Initialization');
Songs := TSongs.Create;
-// Songs.LoadSongList;
+ //Songs.LoadSongList;
Log.LogStatus('Creating 2nd Song Array', 'Initialization');
CatSongs := TCatSongs.Create;
@@ -281,7 +282,7 @@ begin
// PluginManager
Log.BenchmarkStart(1);
Log.LogStatus('PluginManager', 'Initialization');
- DLLMan := TDLLMan.Create; //Load PluginList
+ DLLMan := TDLLMan.Create; // Load PluginList
Log.BenchmarkEnd(1);
Log.LogBenchmark('Loading PluginManager', 1);
@@ -313,14 +314,14 @@ begin
Log.BenchmarkEnd(1);
Log.LogBenchmark('Loading DataBase System', 1);
- //Playlist Manager
+ // Playlist Manager
Log.BenchmarkStart(1);
Log.LogStatus('Playlist Manager', 'Initialization');
PlaylistMan := TPlaylistManager.Create;
Log.BenchmarkEnd(1);
Log.LogBenchmark('Loading Playlist Manager', 1);
- //GoldenStarsTwinkleMod
+ // GoldenStarsTwinkleMod
Log.BenchmarkStart(1);
Log.LogStatus('Effect Manager', 'Initialization');
GoldenRec := TEffectManager.Create;
@@ -332,7 +333,7 @@ begin
begin
Log.BenchmarkStart(1);
Log.LogStatus('Initialize Joystick', 'Initialization');
- Joy := TJoy.Create;
+ Joy := TJoy.Create;
Log.BenchmarkEnd(1);
Log.LogBenchmark('Initializing Joystick', 1);
end;
@@ -341,7 +342,13 @@ begin
Log.LogBenchmark('Loading Time', 0);
Log.LogError('Creating Core');
- Core := TCore.Create('Ultrastar Deluxe Beta', MakeVersion(1,1,0, chr(0)));
+ Core := TCore.Create(
+ USDXShortVersionStr,
+ MakeVersion(USDX_VERSION_MAJOR,
+ USDX_VERSION_MINOR,
+ USDX_VERSION_RELEASE,
+ chr(0))
+ );
Log.LogError('Running Core');
Core.Run;
@@ -349,10 +356,6 @@ begin
//------------------------------
//Start- Mainloop
//------------------------------
- //Music.SetLoop(true);
- //Music.SetVolume(50);
- //Music.Open(SkinPath + 'Menu Music 3.mp3');
- //Music.Play;
Log.LogStatus('Main Loop', 'Initialization');
MainLoop;
@@ -360,16 +363,27 @@ begin
//------------------------------
//Finish Application
//------------------------------
-
+
+ // TODO:
+ // call an uninitialize routine for every initialize step
+ // or at least use the corresponding Free-Methods
+
+ UnloadOpenGL;
+ //TTF_quit();
SDL_Quit();
-
+
{$ifdef WIN32}
- if Ini.LPT = 1 then LCD.Clear;
- if Ini.LPT = 2 then Light.TurnOff;
+ if assigned(LCD) and (Ini.LPT = 1) then
+ LCD.Clear;
+ if assigned(Light) and (Ini.LPT = 2) then
+ Light.TurnOff;
{$endif}
- Log.LogStatus('Main Loop', 'Finished');
- Log.Free;
+ if assigned(Log) then
+ begin
+ Log.LogStatus('Main Loop', 'Finished');
+ Log.Free;
+ end;
end;
end;
@@ -377,80 +391,77 @@ procedure MainLoop;
var
Delay: integer;
begin
- try
- Delay := 0;
- SDL_EnableKeyRepeat(125, 125);
+ Delay := 0;
+ SDL_EnableKeyRepeat(125, 125);
- CountSkipTime(); // JB - for some reason this seems to be needed when we use the SDL Timer functions.
- While not Done do
- Begin
- // joypad
- if (Ini.Joypad = 1) OR (Params.Joypad) then
- Joy.Update;
+ CountSkipTime(); // JB - for some reason this seems to be needed when we use the SDL Timer functions.
+ while not Done do
+ begin
+ // joypad
+ if (Ini.Joypad = 1) or (Params.Joypad) then
+ Joy.Update;
- // keyboard events
- CheckEvents;
+ // keyboard events
+ CheckEvents;
- // display
- done := not Display.Draw;
- SwapBuffers;
+ // display
+ done := not Display.Draw;
+ SwapBuffers;
- // light
- Light.Refresh;
+ // light
+ Light.Refresh;
- // delay
- CountMidTime;
+ // delay
+ CountMidTime;
- Delay := Floor(1000 / 100 - 1000 * TimeMid);
+ Delay := Floor(1000 / 100 - 1000 * TimeMid);
- if Delay >= 1 then
- SDL_Delay(Delay); // dynamic, maximum is 100 fps
+ if Delay >= 1 then
+ SDL_Delay(Delay); // dynamic, maximum is 100 fps
- CountSkipTime;
+ CountSkipTime;
- // reinitialization of graphics
- if Restart then
- begin
- Reinitialize3D;
- Restart := false;
- end;
+ // reinitialization of graphics
+ if Restart then
+ begin
+ Reinitialize3D;
+ Restart := false;
+ end;
- End;
-
- finally
- UnloadOpenGL;
end;
End;
-Procedure CheckEvents;
-//var
-// p: pointer;
-Begin
- if not Assigned(Display.NextScreen) then
- While SDL_PollEvent( @event ) = 1 Do
- Begin
-// beep;
- Case Event.type_ Of
- SDL_QUITEV: begin
+procedure CheckEvents;
+begin
+ if Assigned(Display.NextScreen) then
+ Exit;
+
+ while SDL_PollEvent( @event ) = 1 do
+ begin
+ case Event.type_ of
+ SDL_QUITEV:
+ begin
Display.Fade := 0;
Display.NextScreenWithCheck := nil;
Display.CheckOK := True;
end;
-{ SDL_MOUSEBUTTONDOWN:
- With Event.button Do
- Begin
- If State = SDL_BUTTON_LEFT Then
- Begin
+ {
+ SDL_MOUSEBUTTONDOWN:
+ with Event.button Do
+ begin
+ if State = SDL_BUTTON_LEFT Then
+ begin
//
- End;
- End; // With}
+ end;
+ end;
+ }
SDL_VIDEORESIZE:
- begin
- ScreenW := Event.resize.w;
- ScreenH := Event.resize.h;
+ begin
+ ScreenW := Event.resize.w;
+ ScreenH := Event.resize.h;
- screen := SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_RESIZABLE);
- end;
+ screen := SDL_SetVideoMode(ScreenW, ScreenH, (Ini.Depth+1) * 16, SDL_OPENGL or SDL_RESIZABLE);
+ end;
SDL_KEYDOWN:
begin
// remap the "keypad enter" key to the "standard enter" key
@@ -474,35 +485,32 @@ Begin
SDL_ShowCursor(1);
end;
- glViewPort(0, 0, ScreenW, ScreenH);
+ glViewPort(0, 0, ScreenW, ScreenH);
end
- else
-
- //ScreenShot hack. If Print is pressed-> Make screenshot and Save to Screenshots Path
- if (Event.key.keysym.sym = SDLK_SYSREQ) or (Event.key.keysym.sym = SDLK_PRINT) then
- Display.ScreenShot
+ // ScreenShot hack. If Print is pressed-> Make screenshot and Save to Screenshots Path
+ else if (Event.key.keysym.sym = SDLK_SYSREQ) or (Event.key.keysym.sym = SDLK_PRINT) then
+ Display.ScreenShot
// popup hack... if there is a visible popup then let it handle input instead of underlying screen
// shoud be done in a way to be sure the topmost popup has preference (maybe error, then check)
- else if (ScreenPopupError <> NIL) and (ScreenPopupError.Visible) then
+ else if (ScreenPopupError <> nil) and (ScreenPopupError.Visible) then
done := not ScreenPopupError.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, True)
- else if (ScreenPopupCheck <> NIL) AND (ScreenPopupCheck.Visible) then
+ else if (ScreenPopupCheck <> nil) and (ScreenPopupCheck.Visible) then
done := not ScreenPopupCheck.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, True)
// end of popup hack
-
else
begin
// check for Screen want to Exit
- done := Not Display.ActualScreen^.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, True);
+ done := not Display.CurrentScreen^.ParseInput(Event.key.keysym.sym, Event.key.keysym.unicode, True);
- //If Screen wants to Exit
+ // If Screen wants to Exit
if done then
begin
- //If Question Option is enabled then Show Exit Popup
+ // If Question Option is enabled then Show Exit Popup
if (Ini.AskbeforeDel = 1) then
begin
- Display.ActualScreen^.CheckFadeTo(NIL,'MSG_QUIT_USDX');
+ Display.CurrentScreen^.CheckFadeTo(nil,'MSG_QUIT_USDX');
end
- else //When asking for exit is disabled then simply exit
+ else // When asking for exit is disabled then simply exit
begin
Display.Fade := 0;
Display.NextScreenWithCheck := nil;
@@ -510,19 +518,21 @@ Begin
end;
end;
- end; // if (Not Display.ActualScreen^.ParseInput(Event.key.keysym.scancode, True)) then
+ end;
+ end;
+ {
+ SDL_JOYAXISMOTION:
+ begin
+ beep
end;
-// SDL_JOYAXISMOTION:
-// begin
-// beep
-// end;
+ }
SDL_JOYBUTTONDOWN:
begin
beep
end;
- End; // Case Event.type_
- End; // While
-End; // CheckEvents
+ end; // Case
+ end; // While
+end;
function GetTimeForBeats(BPM, Beats: real): real;
begin
@@ -538,21 +548,27 @@ procedure GetMidBeatSub(BPMNum: integer; var Time: real; var CurBeat: real);
var
NewTime: real;
begin
- if High(CurrentSong.BPM) = BPMNum then begin
+ if High(CurrentSong.BPM) = BPMNum then
+ begin
// last BPM
CurBeat := CurrentSong.BPM[BPMNum].StartBeat + GetBeats(CurrentSong.BPM[BPMNum].BPM, Time);
Time := 0;
- end else begin
+ end
+ else
+ begin
// not last BPM
// count how much time is it for start of the new BPM and store it in NewTime
NewTime := GetTimeForBeats(CurrentSong.BPM[BPMNum].BPM, CurrentSong.BPM[BPMNum+1].StartBeat - CurrentSong.BPM[BPMNum].StartBeat);
// compare it to remaining time
- if (Time - NewTime) > 0 then begin
+ if (Time - NewTime) > 0 then
+ begin
// there is still remaining time
CurBeat := CurrentSong.BPM[BPMNum].StartBeat;
Time := Time - NewTime;
- end else begin
+ end
+ else
+ begin
// there is no remaining time
CurBeat := CurrentSong.BPM[BPMNum].StartBeat + GetBeats(CurrentSong.BPM[BPMNum].BPM, Time);
Time := 0;
@@ -569,7 +585,8 @@ var
// TempTime: real;
begin
Result := 0;
- if Length(CurrentSong.BPM) = 1 then Result := Time * CurrentSong.BPM[0].BPM / 60;
+ if Length(CurrentSong.BPM) = 1 then
+ Result := Time * CurrentSong.BPM[0].BPM / 60;
(* 2 BPMs *)
{ if Length(CurrentSong.BPM) > 1 then begin
@@ -583,24 +600,27 @@ begin
TopBeat := GetBeats(CurrentSong.BPM[1].BPM, Time);
Result := CurBeat + TopBeat;
- end else begin
+ end
+ else
+ begin
(* pierwszy przedzial *)
Result := TopBeat;
end;
- end; // if}
+ end;}
(* more BPMs *)
- if Length(CurrentSong.BPM) > 1 then begin
-
+ if Length(CurrentSong.BPM) > 1 then
+ begin
CurBeat := 0;
CurBPM := 0;
- while (Time > 0) do begin
+ while (Time > 0) do
+ begin
GetMidBeatSub(CurBPM, Time, CurBeat);
Inc(CurBPM);
end;
Result := CurBeat;
- end; // if
+ end;
end;
function GetTimeFromBeat(Beat: integer): real;
@@ -608,29 +628,42 @@ var
CurBPM: integer;
begin
Result := 0;
- if Length(CurrentSong.BPM) = 1 then Result := CurrentSong.GAP / 1000 + Beat * 60 / CurrentSong.BPM[0].BPM;
+ if Length(CurrentSong.BPM) = 1 then
+ Result := CurrentSong.GAP / 1000 + Beat * 60 / CurrentSong.BPM[0].BPM;
(* more BPMs *)
- if Length(CurrentSong.BPM) > 1 then begin
+ if Length(CurrentSong.BPM) > 1 then
+ begin
Result := CurrentSong.GAP / 1000;
CurBPM := 0;
- while (CurBPM <= High(CurrentSong.BPM)) and (Beat > CurrentSong.BPM[CurBPM].StartBeat) do begin
- if (CurBPM < High(CurrentSong.BPM)) and (Beat >= CurrentSong.BPM[CurBPM+1].StartBeat) then begin
+ while (CurBPM <= High(CurrentSong.BPM)) and
+ (Beat > CurrentSong.BPM[CurBPM].StartBeat) do
+ begin
+ if (CurBPM < High(CurrentSong.BPM)) and
+ (Beat >= CurrentSong.BPM[CurBPM+1].StartBeat) then
+ begin
// full range
- Result := Result + (60 / CurrentSong.BPM[CurBPM].BPM) * (CurrentSong.BPM[CurBPM+1].StartBeat - CurrentSong.BPM[CurBPM].StartBeat);
+ Result := Result + (60 / CurrentSong.BPM[CurBPM].BPM) *
+ (CurrentSong.BPM[CurBPM+1].StartBeat - CurrentSong.BPM[CurBPM].StartBeat);
end;
- if (CurBPM = High(CurrentSong.BPM)) or (Beat < CurrentSong.BPM[CurBPM+1].StartBeat) then begin
+ if (CurBPM = High(CurrentSong.BPM)) or
+ (Beat < CurrentSong.BPM[CurBPM+1].StartBeat) then
+ begin
// in the middle
- Result := Result + (60 / CurrentSong.BPM[CurBPM].BPM) * (Beat - CurrentSong.BPM[CurBPM].StartBeat);
+ Result := Result + (60 / CurrentSong.BPM[CurBPM].BPM) *
+ (Beat - CurrentSong.BPM[CurBPM].StartBeat);
end;
Inc(CurBPM);
end;
-{ while (Time > 0) do begin
+ {
+ while (Time > 0) do
+ begin
GetMidBeatSub(CurBPM, Time, CurBeat);
Inc(CurBPM);
- end;}
+ end;
+ }
end; // if}
end;
@@ -662,18 +695,23 @@ begin
Czas.FracBeatD := Frac(Czas.MidBeatD);
// sentences routines
- for PetGr := 0 to 0 do begin;//High(Gracz) do begin
+ for PetGr := 0 to 0 do //High(Gracz)
+ begin;
CP := PetGr;
- // ustawianie starej czesci
+ // ustawianie starej parts
Czas.OldCzesc := Czesci[CP].Akt;
- // wybieranie aktualnej czesci
+ // wybieranie aktualnej parts
for Pet := 0 to Czesci[CP].High do
- if Czas.AktBeat >= Czesci[CP].Czesc[Pet].Start then Czesci[CP].Akt := Pet;
+ begin
+ if Czas.AktBeat >= Czesci[CP].Czesc[Pet].Start then
+ Czesci[CP].Akt := Pet;
+ end;
// czysczenie nut gracza, gdy to jest nowa plansza
// (optymizacja raz na halfbeat jest zla)
- if Czesci[CP].Akt <> Czas.OldCzesc then NewSentence(Sender);
+ if Czesci[CP].Akt <> Czas.OldCzesc then
+ NewSentence(Sender);
end; // for PetGr
@@ -696,9 +734,13 @@ begin
// plynnie przesuwa text
Done := 1;
for N := 0 to Czesci[0].Czesc[Czesci[0].Akt].HighNut do
- if (Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Start <= Czas.MidBeat)
- and (Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Start + Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Dlugosc >= Czas.MidBeat) then
+ begin
+ if (Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Start <= Czas.MidBeat) and
+ (Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Start + Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Dlugosc >= Czas.MidBeat) then
+ begin
Done := (Czas.MidBeat - Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Start) / (Czesci[0].Czesc[Czesci[0].Akt].Nuta[N].Dlugosc);
+ end;
+ end;
N := Czesci[0].Czesc[Czesci[0].Akt].HighNut;
@@ -727,20 +769,22 @@ var
G: Integer;
begin
// czyszczenie nut graczy
- for G := 0 to High(Player) do begin
+ for G := 0 to High(Player) do
+ begin
Player[G].IlNut := 0;
Player[G].HighNut := -1;
SetLength(Player[G].Nuta, 0);
end;
// Add Words to Lyrics
- with Sender do begin
+ with Sender do
+ begin
{LyricMain.AddCzesc(Czesci[0].Akt);
if Czesci[0].Akt < Czesci[0].High then
LyricSub.AddCzesc(Czesci[0].Akt+1)
else
LyricSub.Clear;}
- while (not Lyrics.LineinQueue) AND (Lyrics.LineCounter <= High(Czesci[0].Czesc)) do
+ while (not Lyrics.LineinQueue) and (Lyrics.LineCounter <= High(Czesci[0].Czesc)) do
Lyrics.AddLine(@Czesci[0].Czesc[Lyrics.LineCounter]);
end;
@@ -758,7 +802,8 @@ begin
// ustawia zaznaczenie tekstu
// SingScreen.LyricMain.Selected := -1;
for Pet := 0 to Czesci[0].Czesc[Czesci[0].Akt].HighNut do
- if (Czesci[0].Czesc[Czesci[0].Akt].Nuta[Pet].Start = Czas.AktBeat) then begin
+ if (Czesci[0].Czesc[Czesci[0].Akt].Nuta[Pet].Start = Czas.AktBeat) then
+ begin
// operates on currently beated note
//Todo: Lyrics
//Sender.LyricMain.Selected := Pet;
@@ -768,7 +813,6 @@ begin
//LCD.MoveCursorBR(Sender.LyricMain.SelectedLetter);
LCD.ShowCursor;
-
end;
end;
@@ -786,7 +830,8 @@ begin
AudioPlayback.PlaySound(SoundLib.Click);
// debug system on LPT
- if ((Czas.AktBeatC + Czesci[0].Resolution + Czesci[0].NotesGAP) mod Czesci[0].Resolution = 0) then begin
+ if ((Czas.AktBeatC + Czesci[0].Resolution + Czesci[0].NotesGAP) mod Czesci[0].Resolution = 0) then
+ begin
//LPT_1 := 0;
// Light.LightOne(0, 150);
@@ -802,27 +847,31 @@ begin
end;
for Pet := 0 to Czesci[0].Czesc[Czesci[0].Akt].HighNut do
- if (Czesci[0].Czesc[Czesci[0].Akt].Nuta[Pet].Start = Czas.AktBeatC) then begin
+ begin
+ if (Czesci[0].Czesc[Czesci[0].Akt].Nuta[Pet].Start = Czas.AktBeatC) then
+ begin
// click assist
if Ini.ClickAssist = 1 then
AudioPlayback.PlaySound(SoundLib.Click);
- //LPT_2 := 0;
- if ParamStr(1) <> '-doublelights' then
+ //LPT_2 := 0;
+ if ParamStr(1) <> '-doublelights' then
Light.LightOne(0, 150); //125
-
// drum machine
-(* TempBeat := Czas.AktBeat;// + 2;
+ (*
+ TempBeat := Czas.AktBeat;// + 2;
if (TempBeat mod 8 = 0) then Music.PlayDrum;
if (TempBeat mod 8 = 4) then Music.PlayClap;
// if (TempBeat mod 4 = 2) then Music.PlayHihat;
- if (TempBeat mod 4 <> 0) then Music.PlayHihat;*)
+ if (TempBeat mod 4 <> 0) then Music.PlayHihat;
+ *)
end;
+ end;
- {$IFDEF UseSerialPort}
- // PortWriteB($378, LPT_1 + LPT_2 * 2); // 0 zapala
- {$ENDIF}
+ {$IFDEF UseSerialPort}
+ // PortWriteB($378, LPT_1 + LPT_2 * 2); // 0 zapala
+ {$ENDIF}
end;
procedure NewBeatD(Sender: TScreenSing);
@@ -852,56 +901,59 @@ begin
// beep;
// On linux we get an AV @ NEWNOTE, line 600 of Classes/UMain.pas
- if not assigned( AudioInputProcessor.Sound ) then // TODO : JB_Linux ... why is this now not assigned... it was fine a few hours ago..
+ if not assigned( AudioInputProcessor.Sound ) then
exit;
// analizuje dla obu graczy ten sam sygnal (Sound.OneSrcForBoth)
// albo juz lepiej nie
for CP := 0 to PlayersPlay-1 do
begin
-
// analyze buffer
AudioInputProcessor.Sound[CP].AnalyzeBuffer;
// adds some noise
-// Czas.Ton := Czas.Ton + Round(Random(3)) - 1;
+ //Czas.Ton := Czas.Ton + Round(Random(3)) - 1;
- // 0.5.0: count min and max sentence range for checking (detection is delayed to the notes we see on the screen)
+ // count min and max sentence range for checking (detection is delayed to the notes we see on the screen)
SMin := Czesci[0].Akt-1;
- if SMin < 0 then SMin := 0;
+ if SMin < 0 then
+ SMin := 0;
SMax := Czesci[0].Akt;
// check if we can add new note
Mozna := false;
SDet:=SMin;
for S := SMin to SMax do
+ begin
for Pet := 0 to Czesci[0].Czesc[S].HighNut do
+ begin
if ((Czesci[0].Czesc[S].Nuta[Pet].Start <= Czas.AktBeatD)
and (Czesci[0].Czesc[S].Nuta[Pet].Start + Czesci[0].Czesc[S].Nuta[Pet].Dlugosc - 1 >= Czas.AktBeatD))
and (not Czesci[0].Czesc[S].Nuta[Pet].FreeStyle) // but don't allow when it's FreeStyle note
- and (Czesci[0].Czesc[S].Nuta[Pet].Dlugosc > 0) // and make sure the note lenghts is at least 1
- then begin
- SDet := S;
- Mozna := true;
- Break;
- end;
+ and (Czesci[0].Czesc[S].Nuta[Pet].Dlugosc > 0) then // and make sure the note lenghts is at least 1
+ begin
+ SDet := S;
+ Mozna := true;
+ Break;
+ end;
+ end;
+ end;
S := SDet;
-
-
-
-
-// Czas.SzczytJest := true;
-// Czas.Ton := 27;
+ //Czas.SzczytJest := true;
+ //Czas.Ton := 27;
// gdy moze, to dodaje nute
- if (AudioInputProcessor.Sound[CP].ToneValid) and (Mozna) then begin
+ if (AudioInputProcessor.Sound[CP].ToneValid) and (Mozna) then
+ begin
// operowanie na ostatniej nucie
for Pet := 0 to Czesci[0].Czesc[S].HighNut do
- if (Czesci[0].Czesc[S].Nuta[Pet].Start <= Czas.OldBeatD+1)
- and (Czesci[0].Czesc[S].Nuta[Pet].Start +
- Czesci[0].Czesc[S].Nuta[Pet].Dlugosc > Czas.OldBeatD+1) then begin
+ begin
+ if (Czesci[0].Czesc[S].Nuta[Pet].Start <= Czas.OldBeatD+1) and
+ (Czesci[0].Czesc[S].Nuta[Pet].Start +
+ Czesci[0].Czesc[S].Nuta[Pet].Dlugosc > Czas.OldBeatD+1) then
+ begin
// to robi, tylko dla pary nut (oryginalnej i gracza)
// przesuwanie tonu w odpowiednia game
@@ -919,33 +971,32 @@ begin
//if Ini.Difficulty = 2 then Range := 0;
Range := 2 - Ini.Difficulty;
- if abs(Czesci[0].Czesc[S].Nuta[Pet].Ton - AudioInputProcessor.Sound[CP].Tone) <= Range then begin
+ if abs(Czesci[0].Czesc[S].Nuta[Pet].Ton - AudioInputProcessor.Sound[CP].Tone) <= Range then
+ begin
AudioInputProcessor.Sound[CP].Tone := Czesci[0].Czesc[S].Nuta[Pet].Ton;
-
// Half size Notes Patch
NoteHit := true;
-
if (Ini.LineBonus = 0) then
begin
- // add points without LineBonus
- case Czesci[0].Czesc[S].Nuta[Pet].Wartosc of
- 1: Player[CP].Score := Player[CP].Score + 10000 / Czesci[0].Wartosc *
- Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
- 2: Player[CP].ScoreGolden := Player[CP].ScoreGolden + 10000 / Czesci[0].Wartosc *
- Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
- end;
+ // add points without LineBonus
+ case Czesci[0].Czesc[S].Nuta[Pet].Wartosc of
+ 1: Player[CP].Score := Player[CP].Score + 10000 / Czesci[0].Wartosc *
+ Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
+ 2: Player[CP].ScoreGolden := Player[CP].ScoreGolden + 10000 / Czesci[0].Wartosc *
+ Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
+ end;
end
else
begin
- // add points with Line Bonus
- case Czesci[0].Czesc[S].Nuta[Pet].Wartosc of
- 1: Player[CP].Score := Player[CP].Score + 9000 / Czesci[0].Wartosc *
- Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
- 2: Player[CP].ScoreGolden := Player[CP].ScoreGolden + 9000 / Czesci[0].Wartosc *
- Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
- end;
+ // add points with Line Bonus
+ case Czesci[0].Czesc[S].Nuta[Pet].Wartosc of
+ 1: Player[CP].Score := Player[CP].Score + 9000 / Czesci[0].Wartosc *
+ Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
+ 2: Player[CP].ScoreGolden := Player[CP].ScoreGolden + 9000 / Czesci[0].Wartosc *
+ Czesci[0].Czesc[S].Nuta[Pet].Wartosc;
+ end;
end;
Player[CP].ScoreI := Floor(Player[CP].Score / 10) * 10;
@@ -955,63 +1006,75 @@ begin
end;
end; // operowanie
+ end; // for
// sprawdzanie czy to nowa nuta, czy przedluzenie
- if S = SMax then begin
- Nowa := true;
- // jezeli ostatnia ma ten sam ton
- if (Player[CP].IlNut > 0 )
- and (Player[CP].Nuta[Player[CP].HighNut].Ton = AudioInputProcessor.Sound[CP].Tone)
- and (Player[CP].Nuta[Player[CP].HighNut].Start + Player[CP].Nuta[Player[CP].HighNut].Dlugosc = Czas.AktBeatD)
- then Nowa := false;
- // jezeli jest jakas nowa nuta na sprawdzanym beacie
- for Pet := 0 to Czesci[0].Czesc[S].HighNut do
- if (Czesci[0].Czesc[S].Nuta[Pet].Start = Czas.AktBeatD) then
- Nowa := true;
-
- // dodawanie nowej nuty
- if Nowa then begin
- // nowa nuta
- Player[CP].IlNut := Player[CP].IlNut + 1;
- Player[CP].HighNut := Player[CP].HighNut + 1;
- SetLength(Player[CP].Nuta, Player[CP].IlNut);
- Player[CP].Nuta[Player[CP].HighNut].Start := Czas.AktBeatD;
- Player[CP].Nuta[Player[CP].HighNut].Dlugosc := 1;
- Player[CP].Nuta[Player[CP].HighNut].Ton := AudioInputProcessor.Sound[CP].Tone; // Ton || TonDokl
- Player[CP].Nuta[Player[CP].HighNut].Detekt := Czas.MidBeat;
-
-
- // Half Note Patch
- Player[CP].Nuta[Player[CP].HighNut].Hit := NoteHit;
-
-
- // Log.LogStatus('Nowa Nuta ' + IntToStr(Gracz.Nuta[Gracz.HighNut].Start), 'NewBeat');
-
- end else begin
- // przedluzenie nuty
- Player[CP].Nuta[Player[CP].HighNut].Dlugosc := Player[CP].Nuta[Player[CP].HighNut].Dlugosc + 1;
- end;
+ if S = SMax then
+ begin
+ Nowa := true;
+ // jezeli ostatnia ma ten sam ton
+ if (Player[CP].IlNut > 0 ) and
+ (Player[CP].Nuta[Player[CP].HighNut].Ton = AudioInputProcessor.Sound[CP].Tone) and
+ (Player[CP].Nuta[Player[CP].HighNut].Start + Player[CP].Nuta[Player[CP].HighNut].Dlugosc = Czas.AktBeatD) then
+ begin
+ Nowa := false;
+ end;
+ // jezeli jest jakas nowa nuta na sprawdzanym beacie
+ for Pet := 0 to Czesci[0].Czesc[S].HighNut do
+ begin
+ if (Czesci[0].Czesc[S].Nuta[Pet].Start = Czas.AktBeatD) then
+ Nowa := true;
+ end;
- // check for perfect note and then lit the star (on Draw)
- for Pet := 0 to Czesci[0].Czesc[S].HighNut do
- if (Czesci[0].Czesc[S].Nuta[Pet].Start = Player[CP].Nuta[Player[CP].HighNut].Start)
- and (Czesci[0].Czesc[S].Nuta[Pet].Dlugosc = Player[CP].Nuta[Player[CP].HighNut].Dlugosc)
- and (Czesci[0].Czesc[S].Nuta[Pet].Ton = Player[CP].Nuta[Player[CP].HighNut].Ton) then begin
- Player[CP].Nuta[Player[CP].HighNut].Perfect := true;
+ // dodawanie nowej nuty
+ if Nowa then
+ begin
+ // nowa nuta
+ Player[CP].IlNut := Player[CP].IlNut + 1;
+ Player[CP].HighNut := Player[CP].HighNut + 1;
+ SetLength(Player[CP].Nuta, Player[CP].IlNut);
+ Player[CP].Nuta[Player[CP].HighNut].Start := Czas.AktBeatD;
+ Player[CP].Nuta[Player[CP].HighNut].Dlugosc := 1;
+ Player[CP].Nuta[Player[CP].HighNut].Ton := AudioInputProcessor.Sound[CP].Tone; // Ton || TonDokl
+ Player[CP].Nuta[Player[CP].HighNut].Detekt := Czas.MidBeat;
+
+ // Half Note Patch
+ Player[CP].Nuta[Player[CP].HighNut].Hit := NoteHit;
+
+ //Log.LogStatus('Nowa Nuta ' + IntToStr(Gracz.Nuta[Gracz.HighNut].Start), 'NewBeat');
+ end
+ else
+ begin
+ // przedluzenie nuty
+ Player[CP].Nuta[Player[CP].HighNut].Dlugosc := Player[CP].Nuta[Player[CP].HighNut].Dlugosc + 1;
end;
+ // check for perfect note and then lit the star (on Draw)
+ for Pet := 0 to Czesci[0].Czesc[S].HighNut do
+ begin
+ if (Czesci[0].Czesc[S].Nuta[Pet].Start = Player[CP].Nuta[Player[CP].HighNut].Start) and
+ (Czesci[0].Czesc[S].Nuta[Pet].Dlugosc = Player[CP].Nuta[Player[CP].HighNut].Dlugosc) and
+ (Czesci[0].Czesc[S].Nuta[Pet].Ton = Player[CP].Nuta[Player[CP].HighNut].Ton) then
+ begin
+ Player[CP].Nuta[Player[CP].HighNut].Perfect := true;
+ end;
+ end;
end;// else beep; // if S = SMax
end; // if moze
end; // for CP
-// Log.LogStatus('EndBeat', 'NewBeat');
+ // Log.LogStatus('EndBeat', 'NewBeat');
-//On Sentence End -> For LineBonus + SingBar
-if (sDet >= low(Czesci[0].Czesc)) AND (sDet <= high(Czesci[0].Czesc)) then
-if assigned( Sender ) AND
- ((Czesci[0].Czesc[SDet].Nuta[Czesci[0].Czesc[SDet].HighNut].Start + Czesci[0].Czesc[SDet].Nuta[Czesci[0].Czesc[SDet].HighNut].Dlugosc - 1) = Czas.AktBeatD) then
- Sender.onSentenceEnd(sDet);
+ //On Sentence End -> For LineBonus + SingBar
+ if (sDet >= low(Czesci[0].Czesc)) and (sDet <= high(Czesci[0].Czesc)) then
+ begin
+ if assigned( Sender ) and
+ ((Czesci[0].Czesc[SDet].Nuta[Czesci[0].Czesc[SDet].HighNut].Start + Czesci[0].Czesc[SDet].Nuta[Czesci[0].Czesc[SDet].HighNut].Dlugosc - 1) = Czas.AktBeatD) then
+ begin
+ Sender.onSentenceEnd(sDet);
+ end;
+ end;
end;
@@ -1034,7 +1097,7 @@ procedure InitializePaths;
// Initialize a Path Variable
// After Setting Paths, make sure that Paths exist
- function initialize_path( out aPathVar : String; const aLocation : String ): boolean;
+ function initialize_path( out aPathVar : string; const aLocation : string ): boolean;
var
lWriteable: Boolean;
lAttrib : integer;
@@ -1045,12 +1108,12 @@ procedure InitializePaths;
// Make sure the directory is needex
ForceDirectories(aPathVar);
- If DirectoryExists(aPathVar) then
+ if DirectoryExists(aPathVar) then
begin
lAttrib := fileGetAttr(aPathVar);
- lWriteable := ( lAttrib and faDirectory <> 0 ) AND
- NOT ( lAttrib and faReadOnly <> 0 )
+ lWriteable := (lAttrib and faDirectory <> 0) and
+ not (lAttrib and faReadOnly <> 0)
end;
if not lWriteable then
@@ -1060,7 +1123,6 @@ procedure InitializePaths;
end;
begin
-
initialize_path( LogPath , Platform.GetLogPath );
initialize_path( SoundPath , Platform.GetGameSharedPath + 'Sounds' + PathDelim );
initialize_path( ThemePath , Platform.GetGameSharedPath + 'Themes' + PathDelim );
diff --git a/Game/Code/Classes/URecord.pas b/Game/Code/Classes/URecord.pas
index 8ae0978a..bb8e38e6 100644
--- a/Game/Code/Classes/URecord.pas
+++ b/Game/Code/Classes/URecord.pas
@@ -15,32 +15,44 @@ uses Classes,
UMusic,
UIni;
+const
+ BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz)
+ NumHalftones = 36; // C2-B4 (for Whitney and my high voice)
+
type
- TSound = class
+ TCaptureBuffer = class
private
- BufferNew: TMemoryStream; // buffer for newest samples
+ BufferNew: TMemoryStream; // buffer for newest samples
+
+ function GetToneString: string; // converts a tone to its string represenatation;
public
BufferArray: array[0..4095] of smallint; // newest 4096 samples
- BufferLong: array of TMemoryStream; // full buffer
-
- Index: integer; // index in TAudioInputProcessor.Sound[] (TODO: Remove if not used)
+ BufferLong: TMemoryStream; // full buffer
+ AnalysisBufferSize: integer; // number of samples of BufferArray to analyze
- AnalysisBufferSize: integer; // number of samples to analyze
+ AudioFormat: TAudioFormatInfo;
// pitch detection
ToneValid: boolean; // true if Tone contains a valid value (otherwise it contains noise)
- //Peak: integer; // position of peak on horizontal pivot (TODO: Remove if not used)
- //ToneAccuracy: real; // tone accuracy (TODO: Remove if not used)
- Tone: integer; // TODO: should be a non-unified full range tone (e.g. C2<>C3). Range: 0..NumHalftones-1
- // Note: at the moment it is the same as ToneUnified
- ToneUnified: integer; // tone unified to one octave (e.g. C2=C3=C4). Range: 0-11
- //Scale: real; // FFT scale (TODO: Remove if not used)
-
- // procedures
+ Tone: integer; // tone relative to one octave (e.g. C2=C3=C4). Range: 0-11
+ ToneAbs: integer; // absolute (full range) tone (e.g. C2<>C3). Range: 0..NumHalftones-1
+
+ // methods
+ constructor Create;
+ destructor Destroy; override;
+
+ procedure Clear;
+
procedure ProcessNewBuffer;
- procedure AnalyzeBuffer; // use to analyze sound from buffers to get new pitch
- procedure AnalyzeByAutocorrelation; // we call it to analyze sound by checking Autocorrelation
- function AnalyzeAutocorrelationFreq(Freq: real): real; // use this to check one frequency by Autocorrelation
+ // use to analyze sound from buffers to get new pitch
+ procedure AnalyzeBuffer;
+ // we call it to analyze sound by checking Autocorrelation
+ procedure AnalyzeByAutocorrelation;
+ // use this to check one frequency by Autocorrelation
+ function AnalyzeAutocorrelationFreq(Freq: real): real;
+ function MaxSampleVolume: Single;
+
+ property ToneString: string READ GetToneString;
end;
TAudioInputDeviceSource = record
@@ -54,27 +66,29 @@ type
Description: string; // soundcard name/description
Source: array of TAudioInputDeviceSource; // soundcard input(-source)s
SourceSelected: integer; // unused. What is this good for?
- MicInput: integer; // unused. What is this good for?
- SampleRate: integer; // capture sample-rate (e.g. 44.1kHz -> 44100)
- CaptureChannel: array[0..1] of TSound; // sound(-buffers) used for left/right channel's capture data
+ MicSource: integer; // unused. What is this good for?
- procedure Start(); virtual; abstract;
- procedure Stop(); virtual; abstract;
+ AudioFormat: TAudioFormatInfo; // capture format info (e.g. 44.1kHz SInt16 stereo)
+ CaptureChannel: array of TCaptureBuffer; // sound-buffer references used for mono or stereo channel's capture data
destructor Destroy; override;
+
+ procedure LinkCaptureBuffer(ChannelIndex: integer; Sound: TCaptureBuffer);
+
+ function Start(): boolean; virtual; abstract;
+ procedure Stop(); virtual; abstract;
end;
TAudioInputProcessor = class
- Sound: array of TSound;
- Device: array of TAudioInputDevice;
-
- constructor Create;
+ public
+ Sound: array of TCaptureBuffer; // sound-buffers for every player
+ Device: array of TAudioInputDevice;
- // handle microphone input
- procedure HandleMicrophoneData(Buffer: Pointer; Size: Cardinal;
- InputDevice: TAudioInputDevice);
+ constructor Create;
- function Volume( aChannel : byte ): byte;
+ // handle microphone input
+ procedure HandleMicrophoneData(Buffer: Pointer; Size: Cardinal;
+ InputDevice: TAudioInputDevice);
end;
TAudioInputBase = class( TInterfacedObject, IAudioInput )
@@ -103,11 +117,6 @@ uses
ULog,
UMain;
-const
- CaptureFreq = 44100;
- BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz)
- NumHalftones = 36; // C2-B4 (for Whitney and my high voice)
-
var
singleton_AudioInputProcessor : TAudioInputProcessor = nil;
@@ -136,15 +145,57 @@ var
begin
Stop();
Source := nil;
- for i := 0 to High(CaptureChannel) do
- CaptureChannel[i] := nil;
+ CaptureChannel := nil;
+ FreeAndNil(AudioFormat);
inherited Destroy;
end;
+procedure TAudioInputDevice.LinkCaptureBuffer(ChannelIndex: integer; Sound: TCaptureBuffer);
+begin
+ // check bounds
+ if ((ChannelIndex < 0) or (ChannelIndex > High(CaptureChannel))) then
+ Exit;
+
+ // reset audio-format of old capture-buffer
+ if (CaptureChannel[ChannelIndex] <> nil) then
+ CaptureChannel[ChannelIndex].AudioFormat := nil;
+
+ // set audio-format of new capture-buffer
+ if (Sound <> nil) then
+ Sound.AudioFormat := AudioFormat;
+
+ // replace old with new buffer
+ CaptureChannel[ChannelIndex] := Sound;
+end;
{ TSound }
-procedure TSound.ProcessNewBuffer;
+constructor TCaptureBuffer.Create;
+begin
+ inherited;
+ BufferNew := TMemoryStream.Create;
+ BufferLong := TMemoryStream.Create;
+ AnalysisBufferSize := Min(4*1024, Length(BufferArray));
+end;
+
+destructor TCaptureBuffer.Destroy;
+begin
+ AudioFormat := nil;
+ FreeAndNil(BufferNew);
+ FreeAndNil(BufferLong);
+ inherited;
+end;
+
+procedure TCaptureBuffer.Clear;
+begin
+ if assigned(BufferNew) then
+ BufferNew.Clear;
+ if assigned(BufferLong) then
+ BufferLong.Clear;
+ FillChar(BufferArray[0], Length(BufferArray) * SizeOf(SmallInt), 0);
+end;
+
+procedure TCaptureBuffer.ProcessNewBuffer;
var
SkipCount: integer;
NumSamples: integer;
@@ -155,7 +206,7 @@ begin
NumSamples := BufferNew.Size div 2;
// check if we have more new samples than we can store
- if NumSamples > Length(BufferArray) then
+ if (NumSamples > Length(BufferArray)) then
begin
// discard the oldest of the new samples
SkipCount := NumSamples - Length(BufferArray);
@@ -172,90 +223,96 @@ begin
BufferNew.ReadBuffer(BufferArray[Length(BufferArray)-NumSamples], 2*NumSamples);
// save capture-data to BufferLong if neccessary
- if Ini.SavePlayback = 1 then
+ if (Ini.SavePlayback = 1) then
begin
BufferNew.Seek(0, soBeginning);
- BufferLong[0].CopyFrom(BufferNew, BufferNew.Size);
+ BufferLong.CopyFrom(BufferNew, BufferNew.Size);
end;
end;
-procedure TSound.AnalyzeBuffer;
-begin
- AnalyzeByAutocorrelation;
-end;
-
-procedure TSound.AnalyzeByAutocorrelation;
+procedure TCaptureBuffer.AnalyzeBuffer;
var
- ToneIndex: integer;
- Freq: real;
- Wages: array[0..NumHalftones-1] of real;
- MaxTone: integer;
- MaxWage: real;
Volume: real;
MaxVolume: real;
SampleIndex: integer;
Threshold: real;
-const
- HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
begin
ToneValid := false;
+ ToneAbs := -1;
+ Tone := -1;
// find maximum volume of first 1024 samples
MaxVolume := 0;
for SampleIndex := 0 to 1023 do
begin
- Volume := Abs(BufferArray[SampleIndex]) /
- -Low(Smallint); // was $10000 (65536) before but must be 32768
-
+ Volume := Abs(BufferArray[SampleIndex]) / -Low(Smallint);
if Volume > MaxVolume then
MaxVolume := Volume;
end;
+ case Ini.Threshold of
+ 0: Threshold := 0.05;
+ 1: Threshold := 0.1;
+ 2: Threshold := 0.15;
+ 3: Threshold := 0.2;
+ else Threshold := 0.1;
+ end;
+
+ // check if signal has an acceptable volume (ignore background-noise)
+ if MaxVolume >= Threshold then
+ begin
+ // analyse the current voice pitch
+ AnalyzeByAutocorrelation;
+ ToneValid := true;
+ end;
+end;
+
+procedure TCaptureBuffer.AnalyzeByAutocorrelation;
+var
+ ToneIndex: integer;
+ CurFreq: real;
+ CurWeight: real;
+ MaxWeight: real;
+ MaxTone: integer;
+const
+ HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
+begin
// prepare to analyze
- MaxWage := 0;
+ MaxWeight := -1;
// analyze halftones
+ // Note: at the lowest tone (~65Hz) and a buffer-size of 4096
+ // at 44.1 (or 48kHz) only 6 (or 5) samples are compared, this might be
+ // too few samples -> use a bigger buffer-size
for ToneIndex := 0 to NumHalftones-1 do
begin
- Freq := BaseToneFreq * Power(HalftoneBase, ToneIndex);
- Wages[ToneIndex] := AnalyzeAutocorrelationFreq(Freq);
+ CurFreq := BaseToneFreq * Power(HalftoneBase, ToneIndex);
+ CurWeight := AnalyzeAutocorrelationFreq(CurFreq);
- if Wages[ToneIndex] > MaxWage then
+ // TODO: prefer higher frequencies (use >= or use downto)
+ if (CurWeight > MaxWeight) then
begin
- // this frequency has better wage
- MaxWage := Wages[ToneIndex];
- MaxTone := ToneIndex;
+ // this frequency has a higher weight
+ MaxWeight := CurWeight;
+ MaxTone := ToneIndex;
end;
end;
- Threshold := 0.2;
- case Ini.Threshold of
- 0: Threshold := 0.1;
- 1: Threshold := 0.2;
- 2: Threshold := 0.3;
- 3: Threshold := 0.4;
- end;
-
- // check if signal has an acceptable volume (ignore background-noise)
- if MaxVolume >= Threshold then
- begin
- ToneValid := true;
- ToneUnified := MaxTone mod 12;
- Tone := MaxTone mod 12;
- end;
-
+ ToneAbs := MaxTone;
+ Tone := MaxTone mod 12;
end;
-function TSound.AnalyzeAutocorrelationFreq(Freq: real): real; // result medium difference
+// result medium difference
+function TCaptureBuffer.AnalyzeAutocorrelationFreq(Freq: real): real;
var
- Dist: real; // distance (0=equal .. 1=totally different) between correlated samples
+ Dist: real; // distance (0=equal .. 1=totally different) between correlated samples
AccumDist: real; // accumulated distances
SampleIndex: integer; // index of sample to analyze
CorrelatingSampleIndex: integer; // index of sample one period ahead
SamplesPerPeriod: integer; // samples in one period
begin
SampleIndex := 0;
- SamplesPerPeriod := Round(CaptureFreq/Freq);
+ SamplesPerPeriod := Round(AudioFormat.SampleRate/Freq);
CorrelatingSampleIndex := SampleIndex + SamplesPerPeriod;
AccumDist := 0;
@@ -265,7 +322,7 @@ begin
begin
// calc distance (correlation: 1-dist) to corresponding sample in next period
Dist := Abs(BufferArray[SampleIndex] - BufferArray[CorrelatingSampleIndex]) /
- High(Word); // was $10000 (65536) before but must be 65535
+ High(Word);
AccumDist := AccumDist + Dist;
Inc(SampleIndex);
Inc(CorrelatingSampleIndex);
@@ -275,9 +332,49 @@ begin
Result := 1 - AccumDist / AnalysisBufferSize;
end;
+function TCaptureBuffer.MaxSampleVolume: Single;
+var
+ lSampleIndex: Integer;
+ lMaxVol : Longint;
+begin;
+ // FIXME: lock buffer to avoid race-conditions
+ lMaxVol := 0;
+ for lSampleIndex := 0 to High(BufferArray) do
+ begin
+ if Abs(BufferArray[lSampleIndex]) > lMaxVol then
+ lMaxVol := Abs(BufferArray[lSampleIndex]);
+ end;
+
+ result := lMaxVol / -Low(Smallint);
+end;
+
+const
+ ToneStrings: array[0..11] of string = (
+ 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
+ );
+
+function TCaptureBuffer.GetToneString: string;
+begin
+ if (ToneValid) then
+ Result := ToneStrings[Tone] + IntToStr(ToneAbs div 12 + 2)
+ else
+ Result := '-';
+end;
+
{ TAudioInputProcessor }
+constructor TAudioInputProcessor.Create;
+var
+ i: integer;
+begin
+ SetLength(Sound, 6 {max players});//Ini.Players+1);
+ for i := 0 to High(Sound) do
+ begin
+ Sound[i] := TCaptureBuffer.Create;
+ end;
+end;
+
{*
* Handle captured microphone input data.
* Params:
@@ -289,32 +386,52 @@ end;
*}
procedure TAudioInputProcessor.HandleMicrophoneData(Buffer: Pointer; Size: Cardinal; InputDevice: TAudioInputDevice);
var
- NumSamples: integer; // number of samples
- SampleIndex: integer;
Value: integer;
- ByteBuffer: PByteArray; // buffer handled as array of bytes
+ ChannelBuffer: PChar; // buffer handled as array of bytes (offset relative to channel)
SampleBuffer: PSmallIntArray; // buffer handled as array of samples
- Offset: integer;
Boost: byte;
ChannelCount: integer;
ChannelIndex: integer;
- CaptureChannel: TSound;
- SampleSize: integer;
+ ChannelOffset: integer;
+ CaptureChannel: TCaptureBuffer;
+ AudioFormat: TAudioFormatInfo;
+ FrameSize: integer;
+ NumSamples: integer;
+ NumFrames: integer; // number of frames (stereo: 2xsamples)
+ i: integer;
begin
// set boost
case Ini.MicBoost of
- 0: Boost := 1;
- 1: Boost := 2;
- 2: Boost := 4;
- 3: Boost := 8;
+ 0: Boost := 1;
+ 1: Boost := 2;
+ 2: Boost := 4;
+ 3: Boost := 8;
+ else Boost := 1;
end;
- // boost buffer
- NumSamples := Size div 2;
+ AudioFormat := InputDevice.AudioFormat;
+
+ // FIXME: At the moment we assume a SInt16 format
+ // TODO: use SDL_AudioConvert to convert to SInt16 but do NOT change the
+ // samplerate (SDL does not convert 44.1kHz to 48kHz so we might get wrong
+ // results in the analysis phase otherwise)
+ if (AudioFormat.Format <> asfS16) then
+ begin
+ // this only occurs if a developer choosed a wrong input sample-format
+ Log.CriticalError('TAudioInputProcessor.HandleMicrophoneData: Wrong sample-format');
+ Exit;
+ end;
+
+ // interpret buffer as buffer of bytes
SampleBuffer := Buffer;
- for SampleIndex := 0 to NumSamples-1 do
+
+ NumSamples := Size div SizeOf(Smallint);
+
+ // boost buffer
+ // TODO: remove this senseless stuff - adjust the threshold instead
+ for i := 0 to NumSamples-1 do
begin
- Value := SampleBuffer^[SampleIndex] * Boost;
+ Value := SampleBuffer^[i] * Boost;
// TODO : JB - This will clip the audio... cant we reduce the "Boost" if the data clips ??
if Value > High(Smallint) then
@@ -323,18 +440,12 @@ begin
if Value < Low(Smallint) then
Value := Low(Smallint);
- SampleBuffer^[SampleIndex] := Value;
+ SampleBuffer^[i] := Value;
end;
- // number of channels
- ChannelCount := Length(InputDevice.CaptureChannel);
- // size of one sample
- SampleSize := ChannelCount * SizeOf(SmallInt);
// samples per channel
- NumSamples := Size div SampleSize;
-
- // interpret buffer as buffer of bytes
- ByteBuffer := Buffer;
+ FrameSize := AudioFormat.Channels * SizeOf(SmallInt);
+ NumFrames := Size div FrameSize;
// process channels
for ChannelIndex := 0 to High(InputDevice.CaptureChannel) do
@@ -342,55 +453,21 @@ begin
CaptureChannel := InputDevice.CaptureChannel[ChannelIndex];
if (CaptureChannel <> nil) then
begin
- Offset := ChannelIndex * SizeOf(SmallInt);
+ // set offset according to channel index
+ ChannelBuffer := @PChar(Buffer)[ChannelIndex * SizeOf(SmallInt)];
// TODO: remove BufferNew and write to BufferArray directly
CaptureChannel.BufferNew.Clear;
- for SampleIndex := 0 to NumSamples-1 do
+ for i := 0 to NumFrames-1 do
begin
- CaptureChannel.BufferNew.Write(ByteBuffer^[Offset + SampleIndex*SampleSize],
- SizeOf(SmallInt));
+ CaptureChannel.BufferNew.Write(ChannelBuffer[i*FrameSize], SizeOf(SmallInt));
end;
CaptureChannel.ProcessNewBuffer();
end;
end;
end;
-constructor TAudioInputProcessor.Create;
-var
- i: integer;
-begin
- SetLength(Sound, 6 {max players});//Ini.Players+1);
- for i := 0 to High(Sound) do
- begin
- Sound[i] := TSound.Create;
- Sound[i].Index := i;
- Sound[i].BufferNew := TMemoryStream.Create;
- SetLength(Sound[i].BufferLong, 1);
- Sound[i].BufferLong[0] := TMemoryStream.Create;
- Sound[i].AnalysisBufferSize := Min(4*1024, Length(Sound[i].BufferArray));
- end;
-end;
-
-function TAudioInputProcessor.Volume( aChannel : byte ): byte;
-var
- lSampleIndex: Integer;
- lMaxVol : Word;
-begin;
- with AudioInputProcessor.Sound[aChannel] do
- begin
- lMaxVol := BufferArray[0];
- for lSampleIndex := 1 to High(BufferArray) do
- begin
- if Abs(BufferArray[lSampleIndex]) > lMaxVol then
- lMaxVol := Abs(BufferArray[lSampleIndex]);
- end;
- end;
-
- result := trunc( ( 255 / -Low(Smallint) ) * lMaxVol );
-end;
-
{ TAudioInputBase }
@@ -410,14 +487,13 @@ begin
if (Started) then
CaptureStop();
- Log.BenchmarkStart(1);
-
// reset buffers
for S := 0 to High(AudioInputProcessor.Sound) do
- AudioInputProcessor.Sound[S].BufferLong[0].Clear;
+ AudioInputProcessor.Sound[S].Clear;
// start capturing on each used device
- for DeviceIndex := 0 to High(AudioInputProcessor.Device) do begin
+ for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+ begin
Device := AudioInputProcessor.Device[DeviceIndex];
if not assigned(Device) then
continue;
@@ -431,27 +507,25 @@ begin
Player := DeviceCfg.ChannelToPlayerMap[ChannelIndex]-1;
if (Player < 0) or (Player >= PlayersPlay) then
begin
- Device.CaptureChannel[ChannelIndex] := nil;
+ Device.LinkCaptureBuffer(ChannelIndex, nil);
end
else
begin
- Device.CaptureChannel[ChannelIndex] := AudioInputProcessor.Sound[Player];
+ Device.LinkCaptureBuffer(ChannelIndex, AudioInputProcessor.Sound[Player]);
DeviceUsed := true;
end;
end;
// start device if used
- if (DeviceUsed) then begin
- Log.BenchmarkStart(2);
+ if (DeviceUsed) then
+ begin
+ //Log.BenchmarkStart(2);
Device.Start();
- Log.BenchmarkEnd(2);
- Log.LogBenchmark('Device.Start', 2) ;
+ //Log.BenchmarkEnd(2);
+ //Log.LogBenchmark('Device.Start', 2) ;
end;
end;
- Log.BenchmarkEnd(1);
- Log.LogBenchmark('CaptureStart', 1) ;
-
Started := true;
end;
@@ -465,7 +539,8 @@ var
Device: TAudioInputDevice;
DeviceCfg: PInputDeviceConfig;
begin
- for DeviceIndex := 0 to High(AudioInputProcessor.Device) do begin
+ for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+ begin
Device := AudioInputProcessor.Device[DeviceIndex];
if not assigned(Device) then
continue;
diff --git a/Game/Code/Classes/UThemes.pas b/Game/Code/Classes/UThemes.pas
index bfffb26a..dc56020d 100644
--- a/Game/Code/Classes/UThemes.pas
+++ b/Game/Code/Classes/UThemes.pas
@@ -423,11 +423,10 @@ type
end;
TThemeOptionsRecord = class(TThemeBasic)
- SelectSlideCard: TThemeSelectSlide;
- SelectSlideInput: TThemeSelectSlide;
- SelectSlideChannelL: TThemeSelectSlide;
- SelectSlideChannelR: TThemeSelectSlide;
- ButtonExit: TThemeButton;
+ SelectSlideCard: TThemeSelectSlide;
+ SelectSlideInput: TThemeSelectSlide;
+ SelectSlideChannel: TThemeSelectSlide;
+ ButtonExit: TThemeButton;
end;
TThemeOptionsAdvanced = class(TThemeBasic)
@@ -1183,8 +1182,7 @@ begin
ThemeLoadSelectSlide(OptionsRecord.SelectSlideCard, 'OptionsRecordSelectSlideCard');
ThemeLoadSelectSlide(OptionsRecord.SelectSlideInput, 'OptionsRecordSelectSlideInput');
- ThemeLoadSelectSlide(OptionsRecord.SelectSlideChannelL, 'OptionsRecordSelectSlideChannelL');
- ThemeLoadSelectSlide(OptionsRecord.SelectSlideChannelR, 'OptionsRecordSelectSlideChannelR');
+ ThemeLoadSelectSlide(OptionsRecord.SelectSlideChannel, 'OptionsRecordSelectSlideChannel');
ThemeLoadButton(OptionsRecord.ButtonExit, 'OptionsRecordButtonExit');
//Options Advanced
diff --git a/Game/Code/Screens/UScreenOptionsRecord.pas b/Game/Code/Screens/UScreenOptionsRecord.pas
index b6fb588e..3da29f43 100644
--- a/Game/Code/Screens/UScreenOptionsRecord.pas
+++ b/Game/Code/Screens/UScreenOptionsRecord.pas
@@ -2,36 +2,75 @@ unit UScreenOptionsRecord;
interface
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
{$I switches.inc}
uses
- UMenu, SDL, UDisplay, UMusic, UFiles, UIni, UThemes;
+ UThemes,
+ UMusic,
+ URecord,
+ UMenu;
type
TScreenOptionsRecord = class(TMenu)
private
- Card: integer; // current input device
+ // max. count of input-channels determined for all devices
+ MaxChannelCount: integer;
+
+ // current input device
+ CurrentDeviceIndex: integer;
+ PreviewDeviceIndex: integer;
+
+ // string arrays for select-slide options
+ InputSourceNames: array of string;
+ InputDeviceNames: array of string;
+
+ // dynamic generated themes for channel select-sliders
+ SelectSlideChannelTheme: array of TThemeSelectSlide;
+
+ // indices for widget-updates
+ SelectSlideInputID: integer;
+ SelectSlideChannelID: array of integer;
+ TextPitchID: array of integer;
- SelectSlideInput: integer;
- SelectSlideChannelL: integer;
- SelectSlideChannelR: integer;
+ // interaction IDs
+ ExitButtonIID: integer;
+
+ // dummy data for non-available channels
+ ChannelToPlayerMapDummy: integer;
+
+ // preview channel-buffers
+ PreviewChannel: array of TCaptureBuffer;
+
+ procedure StartPreview;
+ procedure StopPreview;
+ procedure UpdateCard;
public
constructor Create; override;
function Draw: boolean; override;
function ParseInput(PressedKey: Cardinal; ScanCode: byte; PressedDown: Boolean): Boolean; override;
procedure onShow; override;
procedure onHide; override;
- procedure UpdateCard;
end;
implementation
-uses SysUtils,
- UGraphic,
- URecord,
- UDraw,
- UMain,
- ULog;
+uses
+ SysUtils,
+ SDL,
+ OpenGL12,
+ UGraphic,
+ UDraw,
+ UMain,
+ UMenuSelectSlide,
+ UMenuText,
+ UFiles,
+ UDisplay,
+ UIni,
+ ULog;
function TScreenOptionsRecord.ParseInput(PressedKey: Cardinal; ScanCode: byte; PressedDown: Boolean): Boolean;
begin
@@ -52,7 +91,8 @@ begin
end;
SDLK_RETURN:
begin
- if SelInteraction = 5 then begin
+ if (SelInteraction = ExitButtonIID) then
+ begin
Ini.Save;
AudioPlayback.PlaySound(SoundLib.Back);
FadeTo(@ScreenOptions);
@@ -64,21 +104,21 @@ begin
InteractPrev;
SDLK_RIGHT:
begin
- if (SelInteraction >= 0) and (SelInteraction <= 4) then begin
+ if (SelInteraction >= 0) and (SelInteraction < ExitButtonIID) then
+ begin
AudioPlayback.PlaySound(SoundLib.Option);
InteractInc;
end;
-// if SelInteraction = 0 then UpdateCard;
UpdateCard;
end;
SDLK_LEFT:
begin
- if (SelInteraction >= 0) and (SelInteraction <= 4) then begin
+ if (SelInteraction >= 0) and (SelInteraction < ExitButtonIID) then
+ begin
AudioPlayback.PlaySound(SoundLib.Option);
InteractDec;
end;
- UpdateCard;
-// if SelInteraction = 0 then UpdateCard;
+ UpdateCard;
end;
end;
end;
@@ -86,146 +126,386 @@ end;
constructor TScreenOptionsRecord.Create;
var
- SC: integer;
- SCI: integer;
+ DeviceIndex: integer;
+ SourceIndex: integer;
+ ChannelIndex: integer;
InputDevice: TAudioInputDevice;
InputDeviceCfg: PInputDeviceConfig;
+ ChannelTheme: ^TThemeSelectSlide;
+ ButtonTheme: TThemeButton;
begin
inherited Create;
- Card := 0;
-
LoadFromTheme(Theme.OptionsRecord);
- SetLength(ICard, Length(AudioInputProcessor.Device));
- for SC := 0 to High(AudioInputProcessor.Device) do
- ICard[SC] := AudioInputProcessor.Device[SC].Description;
+ // set CurrentDeviceIndex to a valid device
+ if (Length(AudioInputProcessor.Device) > 0) then
+ CurrentDeviceIndex := 0
+ else
+ CurrentDeviceIndex := -1;
- if (Card > High(AudioInputProcessor.Device)) then
- Card := 0;
+ PreviewDeviceIndex := -1;
+ // init sliders if at least one device was detected
if (Length(AudioInputProcessor.Device) > 0) then
begin
- InputDevice := AudioInputProcessor.Device[Card];
+ InputDevice := AudioInputProcessor.Device[CurrentDeviceIndex];
InputDeviceCfg := @Ini.InputDeviceConfig[InputDevice.CfgIndex];
-
- SetLength(IInput, Length(InputDevice.Source));
- for SCI := 0 to High(InputDevice.Source) do
- IInput[SCI] := InputDevice.Source[SCI].Name;
- AddSelectSlide(Theme.OptionsRecord.SelectSlideCard, Card, ICard);
+ // init device-selection slider
+ SetLength(InputDeviceNames, Length(AudioInputProcessor.Device));
+ for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+ begin
+ InputDeviceNames[DeviceIndex] := AudioInputProcessor.Device[DeviceIndex].Description;
+ end;
+ // add device-selection slider (InteractionID: 0)
+ AddSelectSlide(Theme.OptionsRecord.SelectSlideCard, CurrentDeviceIndex, InputDeviceNames);
+
+ // init source-selection slider
+ SetLength(InputSourceNames, Length(InputDevice.Source));
+ for SourceIndex := 0 to High(InputDevice.Source) do
+ begin
+ InputSourceNames[SourceIndex] := InputDevice.Source[SourceIndex].Name;
+ end;
+ // add source-selection slider (InteractionID: 1)
+ SelectSlideInputID := AddSelectSlide(Theme.OptionsRecord.SelectSlideInput,
+ InputDeviceCfg.Input, InputSourceNames);
+
+ // find max. channel count of all devices
+ MaxChannelCount := 0;
+ for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+ begin
+ if (AudioInputProcessor.Device[DeviceIndex].AudioFormat.Channels > MaxChannelCount) then
+ MaxChannelCount := AudioInputProcessor.Device[DeviceIndex].AudioFormat.Channels;
+ end;
- SelectSlideInput := AddSelectSlide(Theme.OptionsRecord.SelectSlideInput,
- InputDeviceCfg^.Input, IInput);
- SelectSlideChannelL := AddSelectSlide(Theme.OptionsRecord.SelectSlideChannelL,
- InputDeviceCfg^.ChannelToPlayerMap[0], IChannel);
- SelectSlideChannelR := AddSelectSlide(Theme.OptionsRecord.SelectSlideChannelR,
- InputDeviceCfg^.ChannelToPlayerMap[1], IChannel);
+ // init channel-to-player mapping sliders
+ SetLength(SelectSlideChannelID, MaxChannelCount);
+ SetLength(SelectSlideChannelTheme, MaxChannelCount);
+ SetLength(TextPitchID, MaxChannelCount);
+
+ for ChannelIndex := 0 to MaxChannelCount-1 do
+ begin
+ // copy reference slide
+ SelectSlideChannelTheme[ChannelIndex] :=
+ Theme.OptionsRecord.SelectSlideChannel;
+ // set current channel-theme
+ ChannelTheme := @SelectSlideChannelTheme[ChannelIndex];
+ // adjust vertical position
+ ChannelTheme.Y := ChannelTheme.Y + ChannelIndex * ChannelTheme.H;
+ // append channel index to name
+ ChannelTheme.Text := ChannelTheme.Text + IntToStr(ChannelIndex+1);
+
+ // add tone-pitch label
+ TextPitchID[ChannelIndex] := AddText(
+ ChannelTheme.X + ChannelTheme.W,
+ ChannelTheme.Y + ChannelTheme.H/2,
+ '-');
+
+ // show/hide widgets depending on whether the channel exists
+ if (ChannelIndex < Length(InputDeviceCfg.ChannelToPlayerMap)) then
+ begin
+ // current device has this channel
+
+ // add slider
+ SelectSlideChannelID[ChannelIndex] := AddSelectSlide(ChannelTheme^,
+ InputDeviceCfg.ChannelToPlayerMap[ChannelIndex], IChannel);
+ end
+ else
+ begin
+ // current device does not have that many channels
+
+ // add slider but hide it and assign a dummy variable to it
+ SelectSlideChannelID[ChannelIndex] := AddSelectSlide(ChannelTheme^,
+ ChannelToPlayerMapDummy, IChannel);
+ SelectsS[SelectSlideChannelID[ChannelIndex]].Visible := false;
+
+ // hide pitch label
+ Text[TextPitchID[ChannelIndex]].Visible := false;
+ end;
+ end;
- AddSelect(Theme.OptionsSound.SelectMicBoost, Ini.MicBoost, IMicBoost);
+ // TODO: move from sound-options to record-options (Themes must be changed first)
+ //AddSelect(Theme.OptionsSound.SelectMicBoost, Ini.MicBoost, IMicBoost);
end;
- AddButton(Theme.OptionsRecord.ButtonExit);
- if (Length(Button[0].Text)=0) then
+ // add Exit-button
+ ButtonTheme := Theme.OptionsRecord.ButtonExit;
+ ButtonTheme.Y := Theme.OptionsRecord.SelectSlideChannel.Y +
+ MaxChannelCount *
+ Theme.OptionsRecord.SelectSlideChannel.H;
+ AddButton(ButtonTheme);
+ if (Length(Button[0].Text) = 0) then
AddButtonText(14, 20, Theme.Options.Description[7]);
+ // store InteractionID
+ ExitButtonIID := MaxChannelCount + 2;
+ // set focus
Interaction := 0;
end;
+procedure TScreenOptionsRecord.UpdateCard;
+var
+ SourceIndex: integer;
+ InputDevice: TAudioInputDevice;
+ InputDeviceCfg: PInputDeviceConfig;
+ ChannelIndex: integer;
+begin
+ Log.LogStatus('Update input-device', 'TScreenOptionsRecord.UpdateCard') ;
+
+ StopPreview();
+
+ // set CurrentDeviceIndex to a valid device
+ if (CurrentDeviceIndex > High(AudioInputProcessor.Device)) then
+ CurrentDeviceIndex := 0;
+
+ // update sliders if at least one device was detected
+ if (Length(AudioInputProcessor.Device) > 0) then
+ begin
+ InputDevice := AudioInputProcessor.Device[CurrentDeviceIndex];
+ InputDeviceCfg := @Ini.InputDeviceConfig[InputDevice.CfgIndex];
+
+ // update source-selection slider
+ SetLength(InputSourceNames, Length(InputDevice.Source));
+ for SourceIndex := 0 to High(InputDevice.Source) do
+ begin
+ InputSourceNames[SourceIndex] := InputDevice.Source[SourceIndex].Name;
+ end;
+ UpdateSelectSlideOptions(Theme.OptionsRecord.SelectSlideInput, SelectSlideInputID,
+ InputSourceNames, InputDeviceCfg.Input);
+
+ // update channel-to-player mapping sliders
+ for ChannelIndex := 0 to MaxChannelCount-1 do
+ begin
+ // show/hide widgets depending on whether the channel exists
+ if (ChannelIndex < Length(InputDeviceCfg.ChannelToPlayerMap)) then
+ begin
+ // current device has this channel
+
+ // show slider
+ UpdateSelectSlideOptions(SelectSlideChannelTheme[ChannelIndex],
+ SelectSlideChannelID[ChannelIndex], IChannel,
+ InputDeviceCfg.ChannelToPlayerMap[ChannelIndex]);
+ SelectsS[SelectSlideChannelID[ChannelIndex]].Visible := true;
+
+ // show pitch label
+ Text[TextPitchID[ChannelIndex]].Visible := true;
+ end
+ else
+ begin
+ // current device does not have that many channels
+
+ // hide slider and assign a dummy variable to it
+ UpdateSelectSlideOptions(SelectSlideChannelTheme[ChannelIndex],
+ SelectSlideChannelID[ChannelIndex], IChannel,
+ ChannelToPlayerMapDummy);
+ SelectsS[SelectSlideChannelID[ChannelIndex]].Visible := false;
+
+ // hide pitch label
+ Text[TextPitchID[ChannelIndex]].Visible := false;
+ end;
+ end;
+ end;
+
+ StartPreview();
+end;
+
procedure TScreenOptionsRecord.onShow;
+var
+ ChannelIndex: integer;
begin
inherited;
Interaction := 0;
- writeln( 'AudioInput.CaptureStart') ;
- PlayersPlay := 2; // TODO : This needs fixing
- AudioInput.CaptureStart;
+ // create preview sound-buffers
+ SetLength(PreviewChannel, MaxChannelCount);
+ for ChannelIndex := 0 to High(PreviewChannel) do
+ PreviewChannel[ChannelIndex] := TCaptureBuffer.Create();
+ StartPreview();
end;
procedure TScreenOptionsRecord.onHide;
+var
+ ChannelIndex: integer;
begin
- AudioInput.CaptureStop;
+ StopPreview();
+
+ // free preview buffers
+ for ChannelIndex := 0 to High(PreviewChannel) do
+ PreviewChannel[ChannelIndex].Free;
+ SetLength(PreviewChannel, 0);
end;
-procedure TScreenOptionsRecord.UpdateCard;
+procedure TScreenOptionsRecord.StartPreview;
var
- SourceIndex: integer;
- InputDevice: TAudioInputDevice;
- InputDeviceCfg: PInputDeviceConfig;
+ ChannelIndex: integer;
+ Device: TAudioInputDevice;
begin
- Log.LogStatus('Update input-device', 'TScreenOptionsRecord.UpdateCard') ;
-
- AudioInput.CaptureStop;
-
- if (Card > High(AudioInputProcessor.Device)) then
- Card := 0;
-
- if (Length(AudioInputProcessor.Device) > 0) then
+ if ((CurrentDeviceIndex >= 0) and
+ (CurrentDeviceIndex <= High(AudioInputProcessor.Device))) then
begin
- InputDevice := AudioInputProcessor.Device[Card];
- InputDeviceCfg := @Ini.InputDeviceConfig[InputDevice.CfgIndex];
-
- SetLength(IInput, Length(InputDevice.Source));
- for SourceIndex := 0 to High(InputDevice.Source) do begin
- IInput[SourceIndex] := InputDevice.Source[SourceIndex].Name;
+ Device := AudioInputProcessor.Device[CurrentDeviceIndex];
+ // set preview channel as active capture channel
+ for ChannelIndex := 0 to High(Device.CaptureChannel) do
+ begin
+ PreviewChannel[ChannelIndex].Clear();
+ Device.LinkCaptureBuffer(ChannelIndex, PreviewChannel[ChannelIndex]);
end;
-
- UpdateSelectSlideOptions(Theme.OptionsRecord.SelectSlideInput, SelectSlideInput, IInput,
- InputDeviceCfg^.Input);
- UpdateSelectSlideOptions(Theme.OptionsRecord.SelectSlideChannelL, SelectSlideChannelL, IChannel,
- InputDeviceCfg^.ChannelToPlayerMap[0]);
- UpdateSelectSlideOptions(Theme.OptionsRecord.SelectSlideChannelR, SelectSlideChannelR, IChannel,
- InputDeviceCfg^.ChannelToPlayerMap[1]);
+ Device.Start();
+ PreviewDeviceIndex := CurrentDeviceIndex;
end;
+end;
- AudioInput.CaptureStart;
+procedure TScreenOptionsRecord.StopPreview;
+var
+ ChannelIndex: integer;
+ Device: TAudioInputDevice;
+begin
+ if ((PreviewDeviceIndex >= 0) and
+ (PreviewDeviceIndex <= High(AudioInputProcessor.Device))) then
+ begin
+ Device := AudioInputProcessor.Device[PreviewDeviceIndex];
+ Device.Stop;
+ for ChannelIndex := 0 to High(Device.CaptureChannel) do
+ Device.CaptureChannel[ChannelIndex] := nil;
+ end;
+ PreviewDeviceIndex := -1;
end;
function TScreenOptionsRecord.Draw: boolean;
+var
+ i: integer;
+ x1, x2, y1, y2: real;
+ R, G, B, RD, GD, BD: real;
+ ChannelIndex: integer;
+ Device: TAudioInputDevice;
+ DeviceCfg: PInputDeviceConfig;
+ SelectSlide: TSelectSlide;
+ ToneBoxWidth: real;
begin
DrawBG;
DrawFG;
- // TODO : this needs to be positioned correctly
- if PlayersPlay = 1 then
- SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 0);
-
- if PlayersPlay = 2 then begin
- SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 0);
- SingDrawOscilloscope(425 + 10*ScreenX, 55, 180, 40, 1);
- end;
+ if ((PreviewDeviceIndex >= 0) and
+ (PreviewDeviceIndex <= High(AudioInputProcessor.Device))) then
+ begin
+ Device := AudioInputProcessor.Device[PreviewDeviceIndex];
+ DeviceCfg := @Ini.InputDeviceConfig[Device.CfgIndex];
+
+ glBegin(GL_QUADS);
+ for ChannelIndex := 0 to High(Device.CaptureChannel) do
+ begin
+ // load player color mapped to current input channel
+ if (DeviceCfg.ChannelToPlayerMap[ChannelIndex] > 0) then
+ begin
+ // set mapped channel to corresponding player-color
+ LoadColor(R, G, B, 'P'+ IntToStr(DeviceCfg.ChannelToPlayerMap[ChannelIndex]) + 'Dark');
+ end
+ else
+ begin
+ // set non-mapped channel to white
+ R := 1; G := 1; B := 1;
+ end;
- if PlayersPlay = 4 then begin
- if ScreenAct = 1 then begin
- SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 0);
- SingDrawOscilloscope(425 + 10*ScreenX, 55, 180, 40, 1);
- end;
- if ScreenAct = 2 then begin
- SingDrawOscilloscope(190 + 10*ScreenX, 55, 180, 40, 2);
- SingDrawOscilloscope(425 + 10*ScreenX, 55, 180, 40, 3);
- end;
- end;
+ // dark player colors
+ RD := 0.2 * R;
+ GD := 0.2 * G;
+ BD := 0.2 * B;
+
+ // channel select slide
+ SelectSlide := SelectsS[SelectSlideChannelID[ChannelIndex]];
+
+ //////////
+ // draw Volume
+ //
+
+ // coordinates for black rect
+ x1 := SelectSlide.TextureSBG.X;
+ x2 := x1 + SelectSlide.TextureSBG.W;
+ y2 := SelectSlide.TextureSBG.Y + SelectSlide.TextureSBG.H;
+ y1 := y2 - 11;
+
+ // draw black background-rect
+ glColor3f(0, 0, 0);
+ glVertex2f(x1, y1);
+ glVertex2f(x2, y1);
+ glVertex2f(x2, y2);
+ glVertex2f(x1, y2);
+
+ // coordinates for volume bar
+ x1 := x1 + 1;
+ x2 := x1 + Trunc((SelectSlide.TextureSBG.W-4) *
+ PreviewChannel[ChannelIndex].MaxSampleVolume()) + 1;
+ y1 := y1 + 1;
+ y2 := y2 - 1;
+
+ // draw volume bar
+ glColor3f(RD, GD, BD);
+ glVertex2f(x1, y1);
+ glColor3f(R, G, B);
+ glVertex2f(x2, y1);
+ glColor3f(R, G, B);
+ glVertex2f(x2, y2);
+ glColor3f(RD, GD, BD);
+ glVertex2f(x1, y2);
+
+ //////////
+ // draw Pitch
+ //
+
+ // calc tone pitch
+ PreviewChannel[ChannelIndex].AnalyzeBuffer();
+
+ // coordinates for black rect
+ x1 := SelectSlide.TextureSBG.X;
+ x2 := x1 + SelectSlide.TextureSBG.W;
+ y1 := SelectSlide.TextureSBG.Y + SelectSlide.TextureSBG.H;
+ y2 := y1 + 11;
+
+ // draw black background-rect
+ glColor3f(0, 0, 0);
+ glVertex2f(x1, y1);
+ glVertex2f(x2, y1);
+ glVertex2f(x2, y2);
+ glVertex2f(x1, y2);
+
+ // coordinates for tone boxes
+ ToneBoxWidth := SelectSlide.TextureSBG.W / NumHalftones;
+ y1 := y1 + 1;
+ y2 := y2 - 1;
+
+ // draw tone boxes
+ for i := 0 to NumHalftones-1 do
+ begin
+ x1 := SelectSlide.TextureSBG.X + i * ToneBoxWidth + 2;
+ x2 := x1 + ToneBoxWidth - 4;
+
+ if ((PreviewChannel[ChannelIndex].ToneValid) and
+ (PreviewChannel[ChannelIndex].ToneAbs = i)) then
+ begin
+ // highlight current tone-pitch
+ glColor3f(1, i / (NumHalftones-1), 0)
+ end
+ else
+ begin
+ // grey other tone-pitches
+ glColor3f(0.3, i / (NumHalftones-1) * 0.3, 0);
+ end;
- if PlayersPlay = 3 then begin
- SingDrawOscilloscope(75 + 10*ScreenX, 95, 100, 20, 0);
- SingDrawOscilloscope(370 + 10*ScreenX, 95, 100, 20, 1);
- SingDrawOscilloscope(670 + 10*ScreenX, 95, 100, 20, 2);
- end;
+ glVertex2f(x1, y1);
+ glVertex2f(x2, y1);
+ glVertex2f(x2, y2);
+ glVertex2f(x1, y2);
+ end;
- if PlayersPlay = 6 then begin
- if ScreenAct = 1 then begin
- SingDrawOscilloscope( 75 + 10*ScreenX, 95, 100, 20, 0);
- SingDrawOscilloscope(370 + 10*ScreenX, 95, 100, 20, 1);
- SingDrawOscilloscope(670 + 10*ScreenX, 95, 100, 20, 2);
- end;
- if ScreenAct = 2 then begin
- SingDrawOscilloscope( 75 + 10*ScreenX, 95, 100, 20, 3);
- SingDrawOscilloscope(370 + 10*ScreenX, 95, 100, 20, 4);
- SingDrawOscilloscope(670 + 10*ScreenX, 95, 100, 20, 5);
+ // update tone-pitch label
+ Text[TextPitchID[ChannelIndex]].Text :=
+ PreviewChannel[ChannelIndex].ToneString;
end;
- end;
+ glEnd;
+ end;
Result := True;
end;