From 6880109d9d38abfed6dececf863fdb7430fbb837 Mon Sep 17 00:00:00 2001 From: tobigun Date: Mon, 3 Mar 2008 03:16:57 +0000 Subject: - 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 --- Game/Code/Classes/UAudioDecoder_FFMpeg.pas | 30 +- Game/Code/Classes/UAudioInput_Bass.pas | 77 ++- Game/Code/Classes/UAudioInput_Portaudio.pas | 307 ++++++------ Game/Code/Classes/UAudioPlayback_Portaudio.pas | 38 +- Game/Code/Classes/UDraw.pas | 2 +- Game/Code/Classes/UIni.pas | 448 +++++++++++------- Game/Code/Classes/ULog.pas | 7 +- Game/Code/Classes/UMain.pas | 624 ++++++++++++++----------- Game/Code/Classes/URecord.pas | 393 +++++++++------- Game/Code/Classes/UThemes.pas | 12 +- Game/Code/Screens/UScreenOptionsRecord.pas | 492 ++++++++++++++----- 11 files changed, 1534 insertions(+), 896 deletions(-) (limited to 'Game/Code') 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; -- cgit v1.2.3