{* UltraStar Deluxe - Karaoke Game * * UltraStar Deluxe is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * $URL$ * $Id$ *} unit UScreenOptionsRecord; interface {$IFDEF FPC} {$MODE Delphi} {$ENDIF} {$I switches.inc} uses UThemes, UMusic, URecord, UMenu; type TDrawState = record ChannelIndex: integer; R, G, B: real; // mapped player color (normal) RD, GD, BD: real; // mapped player color (dark) end; TPeakInfo = record Volume: single; Time: cardinal; end; TScreenOptionsRecord = class(TMenu) private // 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 UTF8String; InputDeviceNames: array of UTF8String; // dynamic generated themes for channel select-sliders SelectSlideChannelTheme: array of TThemeSelectSlide; // indices for widget-updates SelectInputSourceID: integer; SelectSlideChannelID: array of integer; // interaction IDs ExitButtonIID: integer; // dummy data for non-available channels ChannelToPlayerMapDummy: integer; // preview channel-buffers PreviewChannel: array of TCaptureBuffer; ChannelPeak: array of TPeakInfo; // Device source volume SourceVolume: single; NextVolumePollTime: cardinal; procedure StartPreview; procedure StopPreview; procedure UpdateInputDevice; function ValidateSettings: boolean; procedure ChangeVolume(VolumeChange: single); procedure DrawVolume(x, y, Width, Height: single); procedure DrawVUMeter(const State: TDrawState; x, y, Width, Height: single); procedure DrawPitch(const State: TDrawState; x, y, Width, Height: single); public constructor Create; override; function Draw: boolean; override; function ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean; override; procedure OnShow; override; procedure OnHide; override; end; const PeakDecay = 0.2; // strength of peak-decay (reduction after one sec) const BarHeight = 11; // height of each bar (volume/vu-meter/pitch) BarUpperSpacing = 1; // spacing between a bar-area and the previous widget BarLowerSpacing = 3; // spacing between a bar-area and the next widget SourceBarsTotalHeight = BarHeight + BarUpperSpacing + BarLowerSpacing; ChannelBarsTotalHeight = 2*BarHeight + BarUpperSpacing + BarLowerSpacing; implementation uses SysUtils, Math, SDL, gl, TextGL, UGraphic, UDraw, ULanguage, UMain, UMenuSelectSlide, UMenuText, UFiles, UDisplay, UIni, UUnicodeUtils, ULog; function TScreenOptionsRecord.ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean; begin Result := true; if (PressedDown) then begin // Key Down // check normal keys case UCS4UpperCase(CharCode) of Ord('Q'): begin Result := false; Exit; end; Ord('+'): begin // FIXME: add a nice volume-slider instead // or at least provide visualization and acceleration if the user holds the key pressed. ChangeVolume(0.02); end; Ord('-'): begin // FIXME: add a nice volume-slider instead // or at least provide visualization and acceleration if the user holds the key pressed. ChangeVolume(-0.02); end; Ord('T'): begin if ((SDL_GetModState() and KMOD_SHIFT) <> 0) then Ini.ThresholdIndex := (Ini.ThresholdIndex + Length(IThresholdVals) - 1) mod Length(IThresholdVals) else Ini.ThresholdIndex := (Ini.ThresholdIndex + 1) mod Length(IThresholdVals); end; end; // check special keys case PressedKey of SDLK_ESCAPE, SDLK_BACKSPACE: begin // TODO: Show Save/Abort screen if (ValidateSettings()) then begin Ini.Save; AudioPlayback.PlaySound(SoundLib.Back); FadeTo(@ScreenOptions); end; end; SDLK_RETURN: begin if (SelInteraction = ExitButtonIID) then begin if (ValidateSettings()) then begin Ini.Save; AudioPlayback.PlaySound(SoundLib.Back); FadeTo(@ScreenOptions); end; end; end; SDLK_DOWN: InteractNext; SDLK_UP : InteractPrev; SDLK_RIGHT: begin if (SelInteraction >= 0) and (SelInteraction < ExitButtonIID) then begin AudioPlayback.PlaySound(SoundLib.Option); InteractInc; end; UpdateInputDevice; end; SDLK_LEFT: begin if (SelInteraction >= 0) and (SelInteraction < ExitButtonIID) then begin AudioPlayback.PlaySound(SoundLib.Option); InteractDec; end; UpdateInputDevice; end; end; end; end; function TScreenOptionsRecord.ValidateSettings: boolean; var BadPlayer: integer; begin BadPlayer := AudioInputProcessor.ValidateSettings(); if (BadPlayer <> 0) then begin ScreenPopupError.ShowPopup( Format(Language.Translate('ERROR_PLAYER_DEVICE_ASSIGNMENT'), [BadPlayer])); Result := false; end else begin Result := true; end; end; constructor TScreenOptionsRecord.Create; var DeviceIndex: integer; SourceIndex: integer; ChannelIndex: integer; InputDevice: TAudioInputDevice; InputDeviceCfg: PInputDeviceConfig; ChannelTheme: ^TThemeSelectSlide; //ButtonTheme: TThemeButton; WidgetYPos: integer; begin inherited Create; LoadFromTheme(Theme.OptionsRecord); // set CurrentDeviceIndex to a valid device if (Length(AudioInputProcessor.DeviceList) > 0) then CurrentDeviceIndex := 0 else CurrentDeviceIndex := -1; PreviewDeviceIndex := -1; WidgetYPos := 0; // init sliders if at least one device was detected if (Length(AudioInputProcessor.DeviceList) > 0) then begin InputDevice := AudioInputProcessor.DeviceList[CurrentDeviceIndex]; InputDeviceCfg := @Ini.InputDeviceConfig[InputDevice.CfgIndex]; // init device-selection slider SetLength(InputDeviceNames, Length(AudioInputProcessor.DeviceList)); for DeviceIndex := 0 to High(AudioInputProcessor.DeviceList) do begin InputDeviceNames[DeviceIndex] := AudioInputProcessor.DeviceList[DeviceIndex].Name; end; // add device-selection slider (InteractionID: 0) Theme.OptionsRecord.SelectSlideCard.showArrows := true; Theme.OptionsRecord.SelectSlideCard.oneItemOnly := true; 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; Theme.OptionsRecord.SelectSlideInput.showArrows := true; Theme.OptionsRecord.SelectSlideInput.oneItemOnly := true; // add source-selection slider (InteractionID: 1) SelectInputSourceID := AddSelectSlide(Theme.OptionsRecord.SelectSlideInput, InputDeviceCfg.Input, InputSourceNames); // add space for source volume bar WidgetYPos := Theme.OptionsRecord.SelectSlideInput.Y + Theme.OptionsRecord.SelectSlideInput.H + SourceBarsTotalHeight; // find max. channel count of all devices MaxChannelCount := 0; for DeviceIndex := 0 to High(AudioInputProcessor.DeviceList) do begin if (AudioInputProcessor.DeviceList[DeviceIndex].AudioFormat.Channels > MaxChannelCount) then MaxChannelCount := AudioInputProcessor.DeviceList[DeviceIndex].AudioFormat.Channels; end; // init channel-to-player mapping sliders SetLength(SelectSlideChannelID, MaxChannelCount); SetLength(SelectSlideChannelTheme, 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 := WidgetYPos; // calc size of next slide (add space for bars) WidgetYPos := WidgetYPos + ChannelTheme.H + ChannelBarsTotalHeight; // append channel index to name ChannelTheme.Text := ChannelTheme.Text + IntToStr(ChannelIndex+1); // 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], IChannelPlayerTranslated); 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, IChannelPlayerTranslated); SelectsS[SelectSlideChannelID[ChannelIndex]].Visible := false; end; end; Theme.OptionsRecord.SelectThreshold.showArrows := true; //basisbit TODO Theme.OptionsRecord.SelectThreshold.oneItemOnly := true; AddSelectSlide(Theme.OptionsRecord.SelectThreshold, Ini.ThresholdIndex, IThreshold); Theme.OptionsRecord.SelectMicBoost.showArrows := true; Theme.OptionsRecord.SelectMicBoost.oneItemOnly := true; AddSelectSlide(Theme.OptionsRecord.SelectMicBoost, Ini.MicBoost, IMicBoostTranslated); end; // add Exit-button AddButton(Theme.OptionsRecord.ButtonExit); if (Length(Button[0].Text) = 0) then AddButtonText(20, 5, Theme.Options.Description[7]); // AddButtonText(20, 5, Theme.Options.Description[10]); // store InteractionID if (Length(AudioInputProcessor.DeviceList) > 0) then ExitButtonIID := MaxChannelCount + 4 else ExitButtonIID := 2; // set focus Interaction := 0; end; procedure TScreenOptionsRecord.UpdateInputDevice; 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.DeviceList)) then CurrentDeviceIndex := 0; // update sliders if at least one device was detected if (Length(AudioInputProcessor.DeviceList) > 0) then begin InputDevice := AudioInputProcessor.DeviceList[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, SelectInputSourceID, 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], IChannelPlayerTranslated, InputDeviceCfg.ChannelToPlayerMap[ChannelIndex]); SelectsS[SelectSlideChannelID[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], IChannelPlayerTranslated, ChannelToPlayerMapDummy); SelectsS[SelectSlideChannelID[ChannelIndex]].Visible := false; end; end; end; StartPreview(); end; procedure TScreenOptionsRecord.ChangeVolume(VolumeChange: single); var InputDevice: TAudioInputDevice; Volume: single; begin // validate CurrentDeviceIndex if ((CurrentDeviceIndex < 0) or (CurrentDeviceIndex > High(AudioInputProcessor.DeviceList))) then begin Exit; end; InputDevice := AudioInputProcessor.DeviceList[CurrentDeviceIndex]; if not assigned(InputDevice) then Exit; // set new volume Volume := InputDevice.GetVolume() + VolumeChange; InputDevice.SetVolume(Volume); //DebugWriteln('Volume: ' + floattostr(InputDevice.GetVolume)); // volume must be polled again NextVolumePollTime := 0; end; procedure TScreenOptionsRecord.OnShow; var ChannelIndex: integer; begin inherited; // BgMusic distracts too much, pause it SoundLib.PauseBgMusic; Interaction := 0; // create preview sound-buffers SetLength(PreviewChannel, MaxChannelCount); for ChannelIndex := 0 to High(PreviewChannel) do PreviewChannel[ChannelIndex] := TCaptureBuffer.Create(); SetLength(ChannelPeak, MaxChannelCount); UpdateInputDevice(); end; procedure TScreenOptionsRecord.OnHide; var ChannelIndex: integer; begin StopPreview(); // free preview buffers for ChannelIndex := 0 to High(PreviewChannel) do PreviewChannel[ChannelIndex].Free; SetLength(PreviewChannel, 0); SetLength(ChannelPeak, 0); end; procedure TScreenOptionsRecord.StartPreview; var ChannelIndex: integer; Device: TAudioInputDevice; begin if ((CurrentDeviceIndex >= 0) and (CurrentDeviceIndex <= High(AudioInputProcessor.DeviceList))) then begin Device := AudioInputProcessor.DeviceList[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]); FillChar(ChannelPeak[ChannelIndex], SizeOf(TPeakInfo), 0); end; Device.Start(); PreviewDeviceIndex := CurrentDeviceIndex; // volume must be polled again NextVolumePollTime := 0; end; end; procedure TScreenOptionsRecord.StopPreview; var ChannelIndex: integer; Device: TAudioInputDevice; begin if ((PreviewDeviceIndex >= 0) and (PreviewDeviceIndex <= High(AudioInputProcessor.DeviceList))) then begin Device := AudioInputProcessor.DeviceList[PreviewDeviceIndex]; Device.Stop; for ChannelIndex := 0 to High(Device.CaptureChannel) do Device.LinkCaptureBuffer(ChannelIndex, nil); end; PreviewDeviceIndex := -1; end; procedure TScreenOptionsRecord.DrawVolume(x, y, Width, Height: single); var x1, y1, x2, y2: single; VolBarInnerWidth: integer; Volume: single; const VolBarInnerHSpacing = 2; VolBarInnerVSpacing = 1; begin // coordinates for black rect x1 := x; y1 := y; x2 := x1 + Width; y2 := y1 + Height; // init blend mode glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); // draw black background-rect glColor4f(0, 0, 0, 0.8); glBegin(GL_QUADS); glVertex2f(x1, y1); glVertex2f(x2, y1); glVertex2f(x2, y2); glVertex2f(x1, y2); glEnd(); VolBarInnerWidth := Trunc(Width - 2*VolBarInnerHSpacing); // TODO: if no volume is available, show some info (a blue bar maybe) if (SourceVolume >= 0) then Volume := SourceVolume else Volume := 0; // coordinates for first half of the volume bar x1 := x + VolBarInnerHSpacing; x2 := x1 + VolBarInnerWidth * Volume; y1 := y1 + VolBarInnerVSpacing; y2 := y2 - VolBarInnerVSpacing; // draw volume-bar glBegin(GL_QUADS); // draw volume bar glColor3f(0.4, 0.3, 0.3); glVertex2f(x1, y1); glVertex2f(x1, y2); glColor3f(1, 0.1, 0.1); glVertex2f(x2, y2); glVertex2f(x2, y1); glEnd(); { not needed anymore // coordinates for separator x1 := x + VolBarInnerHSpacing; x2 := x1 + VolBarInnerWidth; // draw separator glBegin(GL_LINE_STRIP); glColor4f(0.1, 0.1, 0.1, 0.2); glVertex2f(x1, y2); glColor4f(0.4, 0.4, 0.4, 0.2); glVertex2f((x1+x2)/2, y2); glColor4f(0.1, 0.1, 0.1, 0.2); glVertex2f(x2, y2); glEnd(); } glDisable(GL_BLEND); end; procedure TScreenOptionsRecord.DrawVUMeter(const State: TDrawState; x, y, Width, Height: single); var x1, y1, x2, y2: single; Volume, PeakVolume: single; Delta: single; VolBarInnerWidth: integer; const VolBarInnerHSpacing = 2; VolBarInnerVSpacing = 1; begin // coordinates for black rect x1 := x; y1 := y; x2 := x1 + Width; y2 := y1 + Height; // init blend mode glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); // draw black background-rect glColor4f(0, 0, 0, 0.8); glBegin(GL_QUADS); glVertex2f(x1, y1); glVertex2f(x2, y1); glVertex2f(x2, y2); glVertex2f(x1, y2); glEnd(); VolBarInnerWidth := Trunc(Width - 2*VolBarInnerHSpacing); // vertical positions y1 := y1 + VolBarInnerVSpacing; y2 := y2 - VolBarInnerVSpacing; // coordinates for bevel x1 := x + VolBarInnerHSpacing; x2 := x1 + VolBarInnerWidth; glBegin(GL_QUADS); Volume := PreviewChannel[State.ChannelIndex].MaxSampleVolume(); // coordinates for volume bar x1 := x + VolBarInnerHSpacing; x2 := x1 + VolBarInnerWidth * Volume; // draw volume bar glColor3f(State.RD, State.GD, State.BD); glVertex2f(x1, y1); glVertex2f(x1, y2); glColor3f(State.R, State.G, State.B); glVertex2f(x2, y2); glVertex2f(x2, y1); Delta := (SDL_GetTicks() - ChannelPeak[State.ChannelIndex].Time)/1000; PeakVolume := ChannelPeak[State.ChannelIndex].Volume - Delta*Delta*PeakDecay; // determine new peak-volume if (Volume > PeakVolume) then begin PeakVolume := Volume; ChannelPeak[State.ChannelIndex].Volume := Volume; ChannelPeak[State.ChannelIndex].Time := SDL_GetTicks(); end; x1 := x + VolBarInnerHSpacing + VolBarInnerWidth * PeakVolume; x2 := x1 + 2; // draw peak glColor3f(0.8, 0.8, 0.8); glVertex2f(x1, y1); glVertex2f(x1, y2); glVertex2f(x2, y2); glVertex2f(x2, y1); // draw threshold x1 := x + VolBarInnerHSpacing; x2 := x1 + VolBarInnerWidth * IThresholdVals[Ini.ThresholdIndex]; glColor4f(0.3, 0.3, 0.3, 0.6); glVertex2f(x1, y1); glVertex2f(x1, y2); glVertex2f(x2, y2); glVertex2f(x2, y1); glEnd(); glDisable(GL_BLEND); end; procedure TScreenOptionsRecord.DrawPitch(const State: TDrawState; x, y, Width, Height: single); var x1, y1, x2, y2: single; i: integer; ToneBoxWidth: real; ToneString: string; ToneStringWidth, ToneStringHeight: real; ToneStringMaxWidth: real; ToneStringCenterXOffset: real; const PitchBarInnerHSpacing = 2; PitchBarInnerVSpacing = 1; begin // calc tone pitch PreviewChannel[State.ChannelIndex].AnalyzeBuffer(); // coordinates for black rect x1 := x; y1 := y; x2 := x + Width; y2 := y + Height; // init blend mode glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); // draw black background-rect glColor4f(0, 0, 0, 0.8); glBegin(GL_QUADS); glVertex2f(x1, y1); glVertex2f(x2, y1); glVertex2f(x2, y2); glVertex2f(x1, y2); glEnd(); // coordinates for tone boxes ToneBoxWidth := Width / NumHalftones; y1 := y1 + PitchBarInnerVSpacing; y2 := y2 - PitchBarInnerVSpacing; glBegin(GL_QUADS); // draw tone boxes for i := 0 to NumHalftones-1 do begin x1 := x + i * ToneBoxWidth + PitchBarInnerHSpacing; x2 := x1 + ToneBoxWidth - 2*PitchBarInnerHSpacing; if ((PreviewChannel[State.ChannelIndex].ToneValid) and (PreviewChannel[State.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; glVertex2f(x1, y1); glVertex2f(x2, y1); glVertex2f(x2, y2); glVertex2f(x1, y2); end; glEnd(); glDisable(GL_BLEND); /// // draw the name of the tone /////// ToneString := PreviewChannel[State.ChannelIndex].ToneString; ToneStringHeight := ChannelBarsTotalHeight; // initialize font // TODO: what about reflection, italic etc.? SetFontSize(ToneStringHeight); // center // Note: for centering let us assume that G#4 has the max. horizontal extent ToneStringWidth := glTextWidth(ToneString); ToneStringMaxWidth := glTextWidth('G#4'); ToneStringCenterXOffset := (ToneStringMaxWidth-ToneStringWidth) / 2; // draw SetFontPos(x-ToneStringWidth-ToneStringCenterXOffset, y-ToneStringHeight/2); glColor3f(0, 0, 0); glPrint(ToneString); end; function TScreenOptionsRecord.Draw: boolean; var Device: TAudioInputDevice; DeviceCfg: PInputDeviceConfig; SelectSlide: TSelectSlide; BarXOffset, BarYOffset, BarWidth: real; ChannelIndex: integer; State: TDrawState; begin DrawBG; DrawFG; if ((PreviewDeviceIndex >= 0) and (PreviewDeviceIndex <= High(AudioInputProcessor.DeviceList))) then begin Device := AudioInputProcessor.DeviceList[PreviewDeviceIndex]; DeviceCfg := @Ini.InputDeviceConfig[Device.CfgIndex]; // update source volume if (SDL_GetTicks() >= NextVolumePollTime) then begin NextVolumePollTime := SDL_GetTicks() + 500; // next poll in 500ms SourceVolume := Device.GetVolume(); end; // get source select slide SelectSlide := SelectsS[SelectInputSourceID]; BarXOffset := SelectSlide.TextureSBG.X; BarYOffset := SelectSlide.TextureSBG.Y + SelectSlide.TextureSBG.H + BarUpperSpacing; BarWidth := SelectSlide.TextureSBG.W; DrawVolume(SelectSlide.TextureSBG.X, BarYOffset, BarWidth, BarHeight); for ChannelIndex := 0 to High(Device.CaptureChannel) do begin // load player color mapped to current input channel if (DeviceCfg.ChannelToPlayerMap[ChannelIndex] <> CHANNEL_OFF) then begin // set mapped channel to corresponding player-color LoadColor(State.R, State.G, State.B, 'P'+ IntToStr(DeviceCfg.ChannelToPlayerMap[ChannelIndex]) + 'Dark'); end else begin // set non-mapped channel to white State.R := 1; State.G := 1; State.B := 1; end; // dark player colors State.RD := 0.2 * State.R; State.GD := 0.2 * State.G; State.BD := 0.2 * State.B; // channel select slide SelectSlide := SelectsS[SelectSlideChannelID[ChannelIndex]]; BarXOffset := SelectSlide.TextureSBG.X; BarYOffset := SelectSlide.TextureSBG.Y + SelectSlide.TextureSBG.H + BarUpperSpacing; BarWidth := SelectSlide.TextureSBG.W; State.ChannelIndex := ChannelIndex; DrawVUMeter(State, BarXOffset, BarYOffset, BarWidth, BarHeight); DrawPitch(State, BarXOffset, BarYOffset+BarHeight, BarWidth, BarHeight); end; end; Result := true; end; end.