unit VolumeController;

//{$DEFINE KOL_MCK}
interface

uses Windows, Messages, MMSystem {$IFDEF KOL_MCK}, KOL {$ELSE}, Math{$ENDIF};

type
  {$IFDEF KOL_MCK}
  PVolumeCtrl = ^TVolumeCtrl;
  TVolumeController = PVolumeCtrl;
  TVolumeCtrl = object(TObj)
  {$ELSE}
  TVolumeCtrl = class
  {$ENDIF}
  private
    FmixerHandle: Cardinal;
    FMuteControl: MixerControl;
    FVolControl: MixerControl;
    FChannels: Byte;
    FVolChannels: array [Byte] of TMixerControlDetails_Unsigned;

    procedure InitMixerControls;
    procedure UnInitMixerControls;

    procedure SetMute(const Value: Boolean);
    function GetMute: Boolean;
    function GetVolumeChannels(Channel: Byte): DWORD;
    procedure SetVolumeChannels(Channel: Byte; const Value: DWORD);
    function GetVolume: DWORD;
    procedure SetVolume(const Value: DWORD);
    function GetBalance: Double;
    procedure SetBalance(const Value: Double);

  public
    destructor Destroy; {$IFDEF KOL_MCK} virtual; {$ELSE} override; {$ENDIF}
    {$IFNDEF KOL_MCK}
    constructor Create;
    {$ENDIF}

    property Mute: Boolean read GetMute write SetMute;

    procedure GetChannels;
    procedure SetChannels;

    property Channels: Byte read FChannels;
    property VolumeChannels[Channel: Byte]: DWORD read GetVolumeChannels write SetVolumeChannels;

    property Volume: DWORD read GetVolume write SetVolume;
    property Balance: Double read GetBalance write SetBalance;

    property MinVolume: DWORD read FVolControl.Bounds.dwMinimum;
    property MaxVolume: DWORD read FVolControl.Bounds.dwMaximum;
  end;

  {$IFDEF KOL_MCK}
  function NewVolumeController: TVolumeController;
  {$ELSE}
  TVolumeController = TVolumeCtrl;
  {$ENDIF}

implementation

{$IFDEF KOL_MCK}
function NewVolumeController: TVolumeController;
begin
    New(Result, Create);
    Result.InitMixerControls;
    Result.GetVolume;
end;
{$ELSE}
constructor TVolumeCtrl.Create;
begin
    inherited;
    InitMixerControls;
    GetVolume;
end;
{$ENDIF}

destructor TVolumeCtrl.Destroy;
begin
    UnInitMixerControls;
    inherited;
end;

procedure TVolumeCtrl.InitMixerControls;
var LineControls: TMixerLineControls;
    MixerLine: TMixerLine;
    Controls: array [0 .. 30] of TMixerControl;
    i: integer;
begin
    ZeroMemory(@Controls[0], sizeof(TMixerControl) * 31);
    mixerOpen(@FMixerHandle, 0, 0, 0, MIXER_OBJECTF_MIXER);

    ZeroMemory(@MixerLine, sizeof(TMixerLine));
    MixerLine.cbStruct:= sizeof(TMixerLine);
    MixerLine.dwDestination:= 1;
    MixerLine.dwComponentType:= MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
    mixerGetLineInfo(FMixerHandle, @MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
    FChannels:= MixerLine.cChannels;

    ZeroMemory(@LineControls, sizeof(TMixerLineControls));
    LineControls.cbStruct:= sizeof(TMixerLineControls);
    LineControls.dwControlType:= MIXERCONTROL_CONTROLTYPE_VOLUME;
    LineControls.dwLineID:= MixerLine.dwLineID;
    LineControls.cControls:= MixerLine.cControls;
    LineControls.cbmxctrl:= sizeof(MIXERCONTROL);
    LineControls.pamxctrl:= @Controls[0];
    mixerGetLineControls(FMixerHandle, @LineControls, MIXER_GETLINECONTROLSF_ALL);
    for i:= 31 downto 0 do begin
      if Controls[i].dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME then FVolControl:= Controls[i];
      if Controls[i].dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE then FMuteControl:= Controls[i];
    end;
    FVolControl.Metrics.dwReserved[1]:= 1;
    FVolControl.Metrics.dwReserved[2]:= 1;
end;

procedure TVolumeCtrl.UnInitMixerControls;
begin
    mixerClose(FMixerHandle);
end;

procedure TVolumeCtrl.GetChannels;
var ControlDetails: TMixerControlDetails;
begin
    ZeroMemory(@ControlDetails, SizeOf(TMixerControlDetails));
    ZeroMemory(@FVolChannels, FChannels*sizeof(TMixerControlDetails_Unsigned));
    ControlDetails.cbStruct:= sizeof(TMixerControlDetails);
    ControlDetails.dwControlID:= FVolControl.dwControlID;
    ControlDetails.cbDetails:= sizeof(integer);
    ControlDetails.hwndOwner:= 0;
    ControlDetails.cChannels:= FChannels;
    ControlDetails.paDetails:= @FVolChannels;
    MixerGetControlDetails(FmixerHandle, @ControlDetails, MIXER_GETCONTROLDETAILSF_VALUE);
end;

procedure TVolumeCtrl.SetChannels;
var ControlDetails: TMixerControlDetails;
begin
    ZeroMemory(@ControlDetails, sizeof(TMixerControlDetails));
    ControlDetails.cbStruct:= sizeof(TMixerControlDetails);
    ControlDetails.dwControlID:= FVolControl.dwControlID;
    ControlDetails.cChannels:= FChannels;
    ControlDetails.cMultipleItems:= 0;
    ControlDetails.cbDetails:= sizeof(TMixerControlDetails_Unsigned);
    ControlDetails.paDetails:= @FVolChannels;
    mixerSetControlDetails(Fmixerhandle, @ControlDetails, MIXER_SETCONTROLDETAILSF_VALUE);
end;


procedure TVolumeCtrl.SetMute(const Value: Boolean);
var ControlDetails: TMixerControlDetails;
    Details: TMixerControlDetails_Unsigned;
begin
    ZeroMemory(@ControlDetails, sizeof(TMixerControlDetails));
    ControlDetails.cbStruct:= sizeof(TMixerControlDetails);
    ControlDetails.dwControlID:= FMuteControl.dwControlID;
    ControlDetails.cbDetails:= sizeof(integer);
    ControlDetails.hwndOwner:= 0;
    ControlDetails.cChannels:= 1;
    ControlDetails.paDetails:= @Details;
    Details.dwValue:= DWORD(Value);
    MixerSetControlDetails(Fmixerhandle, @ControlDetails, MIXER_GETCONTROLDETAILSF_VALUE);
end;

function TVolumeCtrl.GetMute: Boolean;
var Details: TMixerControlDetails_Unsigned;
    ControlDetails: TMixerControlDetails;
begin
    ZeroMemory(@ControlDetails, sizeof(TMixerControlDetails));
    ControlDetails.cbStruct:= sizeof(TMixerControlDetails);
    ControlDetails.dwControlID:= FMuteControl.dwControlID;
    ControlDetails.cbDetails:= sizeof(integer);
    ControlDetails.hwndOwner:= 0;
    ControlDetails.cChannels:= 1;
    ControlDetails.paDetails:= @Details;
    MixerGetControlDetails(FmixerHandle, @ControlDetails, MIXER_GETCONTROLDETAILSF_VALUE);
    Result:= Details.dwValue <> 0;
end;

function TVolumeCtrl.GetVolumeChannels(Channel: Byte): DWORD;
begin
    Result:= 0;
    if Channel > FChannels then Exit;
    GetChannels;
    Result:= FVolChannels[Channel].dwValue;
end;

procedure TVolumeCtrl.SetVolumeChannels(Channel: Byte; const Value: DWORD);
begin
    if Channel > FChannels then Exit;
    FVolChannels[Channel].dwValue:= Value;
    SetChannels;
end;

function TVolumeCtrl.GetVolume: DWORD;
var i: Byte;
begin
    Result:= 0;
    GetChannels;
    for i:= 0 to FChannels - 1 do Result:= Max(Result, FVolChannels[i].dwValue);
end;

procedure TVolumeCtrl.SetVolume(const Value: DWORD);
var i: Byte;
    Vol: DWORD;
begin
    Vol:= 0; 
    for i:= 0 to FChannels - 1 do Vol:= Max(Vol, FVolChannels[i].dwValue);
    for i:= 0 to FChannels - 1 do
      if Vol = 0 then FVolChannels[i].dwValue:= Value
      else FVolChannels[i].dwValue:= Round(Value * FVolChannels[i].dwValue/Vol);
    SetChannels;
end;

function TVolumeCtrl.GetBalance: Double;
var Vol: DWORD;
begin
    Result:= 50;
    if FChannels <> 2 then Exit;
    GetVolume;
    Vol:= FVolChannels[0].dwValue + FVolChannels[1].dwValue;
    if Vol = 0 then Exit;
    Result:= FVolChannels[1].dwValue / Vol * 100;
end;

procedure TVolumeCtrl.SetBalance(const Value: Double);
var Vol: DWORD;
    i: Byte;
begin
    if FChannels <> 2 then Exit;
    Vol:= 0; 
    for i:= 0 to FChannels - 1 do Vol:= Max(Vol, FVolChannels[i].dwValue);
    if Value < 50 then begin
      FVolChannels[0].dwValue:= Vol;
      FVolChannels[1].dwValue:= Round(Value*Vol/(100-Value));
    end
    else begin
      FVolChannels[1].dwValue:= Vol;
      FVolChannels[0].dwValue:= Round((100-Value)*Vol/(Value));
    end;
    SetChannels;
end;

end.
