{===============================================================} { } { Mixer controls } { a code by Lake Unanimated / unanimated@geocities.com } { } { } { Copyright (c) 2000 Lake of Soft, Ltd } { All Rights Reserved } { } {===============================================================} {$B-} unit MsMixerControl; interface uses Classes, Controls, MsMixerThorax, Contnrs, MMSystem, ComCtrls, Graphics, ExtCtrls; type TvcMixerLine = class; TvcMixer = class; TvcMixerControl = class(tComponent) private fMaster : tvcMixerLine; fControl: tMsMixerControl; fPBList : tList; fTBList : tList; fMBList : tList; fCBList : tList; fClickMBTag: Integer; procedure ClearLists; procedure MyOnTrackChange(Sender: tObject); procedure MyOnMuxButtonClick(Sender: tObject); procedure MyOnMixPMClick(Sender: tObject); procedure MyOnMuxPMClick(Sender: tObject); procedure MyOnCBClick(Sender: tObject); protected procedure OnChange; virtual; public constructor CreateMixerControl(aMaster: tvcMixerLine; aControl: tMsMixerControl); destructor Destroy; override; procedure RecreateControl; // property Control: tMsMixerControl read fControl; end; TvcMixerLine = class(tWinControl) private fLine : tMsMixerLine; fLeftSide: Integer; fTopSide : Integer; fThread : tThread; function GetMixer: tvcMixer; procedure AddLeft(aDelta: Integer); procedure RecreateControls; protected procedure OnChange(FromThread: Boolean = False); virtual; public constructor CreateMixerLine(aMixer: tvcMixer; aLine: tMsMixerLine); destructor Destroy; override; // property Mixer: tvcMixer read GetMixer; end; tMMLineChangeEvent = record Msg : Cardinal; rMixer : hMixer; rLineID : Cardinal; Result : Longint; end; tMMControlChangeEvent = record Msg : Cardinal; rMixer : hMixer; rControlID: Cardinal; Result : Longint; end; tMixerLineDest = (ldPlayback, ldRecording, ldCustom, ldAll); TvcMixer = class(tWinControl) private fMixer : tMsMixerSystem; fActive : Boolean; fLinesDest : tMixerLineDest; fLineColor : tColor; fPMBias : Integer; fPMScale : Integer; fPMShift : Integer; fLineWidth : Integer; fMixerIndex: Integer; fLineDest : Cardinal; fShowDL : Boolean; procedure SetActive(const Value: Boolean); protected procedure DoOpen; virtual; procedure DoClose; virtual; procedure DoLineChange(aMixerID, aLineID: Cardinal); virtual; procedure DoControlChange(aMixerID, aControlID: Cardinal); virtual; procedure MMLineChange(var Msg: tMMLineChangeEvent); message MM_MIXM_LINE_CHANGE; procedure MMControlChange(var Msg: tMMControlChangeEvent); message MM_MIXM_CONTROL_CHANGE; public constructor Create(Owner: tComponent); override; destructor Destroy; override; procedure Open; procedure Close; // property Mixer : tMsMixerSystem read fMixer; property MixerIndex: Integer read fMixerIndex write fMixerIndex; published property Active: Boolean read fActive write SetActive default False; property LinesDestMode : tMixerLineDest read fLinesDest write fLinesDest default ldPlayback; // LineDest is used only when LinesDestMode = ldCustom property LineDest : Cardinal read fLineDest write fLineDest; property ShowDisconnectedLines: Boolean read fShowDL write fShowDL; property LineColor : tColor read fLineColor write fLineColor default clBtnFace; property PeakMeterBias : Integer read fPMBias write fPMBias default 0; property PeakMeterScale: Integer read fPMScale write fPMScale default 1; property PeakMeterFalloffSpeed: Integer read fPMShift write fPMShift default 3; property LineWidth : Integer read fLineWidth write fLineWidth default 70; end; implementation uses Windows, StdCtrls, Menus; type tMixerLineMeterThread = class(tThread) private fMaster: tvcMixerLine; procedure DoUpdate; protected procedure Execute; override; public constructor Create(aMaster: tvcMixerLine); end; { tMixerControlMeterThread } constructor tMixerLineMeterThread.Create(aMaster: tvcMixerLine); begin fMaster := aMaster; FreeOnTerminate := False; inherited Create(True); end; procedure tMixerLineMeterThread.DoUpdate; begin fMaster.OnChange(True); end; procedure tMixerLineMeterThread.Execute; begin while not Terminated do begin Synchronize(DoUpdate); Sleep(70); end; end; { TvcMixerControl } procedure TvcMixerControl.ClearLists; begin fPBList.Clear; fTBList.Clear; fMBList.Clear; fCBList.Clear; end; constructor TvcMixerControl.CreateMixerControl(aMaster: tvcMixerLine; aControl: tMsMixerControl); begin inherited Create(aMaster); fMaster := aMaster; fControl := aControl; fPBList := tList.Create; fTBList := tList.Create; fMBList := tList.Create; fCBList := tList.Create; end; destructor TvcMixerControl.Destroy; begin fPBList.Free; fTBList.Free; fMBList.Free; fCBList.Free; inherited; end; procedure TvcMixerControl.MyOnCBClick(Sender: tObject); begin with (Sender as tCheckBox) do fControl.Details_Boolean[Tag, 0] := Checked; end; procedure TvcMixerControl.MyOnMixPMClick(Sender: tObject); begin with (Sender as tMenuItem), fControl do begin Checked := not Checked; Details_Boolean[fClickMBTag, Tag] := Checked; end; end; procedure TvcMixerControl.MyOnMuxButtonClick(Sender: tObject); begin with (Sender as tButton) do if Assigned(PopupMenu) then begin PopupMenu.Popup(ClientOrigin.x, ClientOrigin.y + Height); fClickMBTag := Tag; end; end; procedure TvcMixerControl.MyOnMuxPMClick(Sender: tObject); var i: Integer; begin with (Sender as tMenuItem), fControl do begin BeginUpdate; try for i := 0 to ControlCaps.cMultipleItems - 1 do Details_Boolean[fClickMBTag, i] := (i = Tag); finally EndUpdate; end; end; end; procedure TvcMixerControl.MyOnTrackChange(Sender: tObject); begin with (Sender as tTrackBar) do Control.Details_Unsigned[Tag, 0] := ($FFFF - Position); end; procedure TvcMixerControl.OnChange; var i: Integer; c: Integer; M: Integer; D: Integer; H: string; begin if (csDestroying in ComponentState) then Exit; fControl.NeedUpdateDetails := True; case fControl.ControlCaps.dwControlType of // custom MIXERCONTROL_CONTROLTYPE_CUSTOM: ; // fader {done}MIXERCONTROL_CONTROLTYPE_BASS, {done}MIXERCONTROL_CONTROLTYPE_TREBLE, {done}MIXERCONTROL_CONTROLTYPE_FADER, {done}MIXERCONTROL_CONTROLTYPE_VOLUME: with fTBList do for i := 0 to Count - 1 do with tTrackBar(Items[i]) do Position := ($FFFF - fControl.Details_Unsigned[Tag, 0]); MIXERCONTROL_CONTROLTYPE_EQUALIZER : ; // list {done?}MIXERCONTROL_CONTROLTYPE_MIXER, {done?}MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT, {done}MIXERCONTROL_CONTROLTYPE_SINGLESELECT, {done}MIXERCONTROL_CONTROLTYPE_MUX : for i := 0 to fMBList.Count - 1 do with tButton(fMBList.Items[i]), PopupMenu do begin H := fControl.ControlCaps.szName; for c := 2 to Items.Count - 1 do begin Items[c].Checked := fControl.Details_Boolean[Tag, c - 2]; if Items[c].Checked then H := H + ' - ' + fControl.ListTextItems[c - 2]; end; Hint := H; end; // meter {done}MIXERCONTROL_CONTROLTYPE_BOOLEANMETER, {done}MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER, {done}MIXERCONTROL_CONTROLTYPE_SIGNEDMETER, {done}MIXERCONTROL_CONTROLTYPE_PEAKMETER: with fPBList, fMaster do for i := 0 to Count - 1 do with tProgressBar(Items[i]) do begin M := (fControl.Details_Signed[Tag, 0] + Mixer.fPMBias) * Mixer.fPMScale; if (M > Position) then D := M - Position { jump up faster } else D := Abs(Position - M) shr Mixer.fPMShift; if (D > 0) then if (M > Position) then Position := Position + D else Position := Position - D; end; // number MIXERCONTROL_CONTROLTYPE_DECIBELS : ; MIXERCONTROL_CONTROLTYPE_PERCENT : ; MIXERCONTROL_CONTROLTYPE_SIGNED : ; MIXERCONTROL_CONTROLTYPE_UNSIGNED : ; // slider MIXERCONTROL_CONTROLTYPE_PAN : ; MIXERCONTROL_CONTROLTYPE_QSOUNDPAN : ; MIXERCONTROL_CONTROLTYPE_SLIDER : ; // switch {done}MIXERCONTROL_CONTROLTYPE_ONOFF, {done}MIXERCONTROL_CONTROLTYPE_STEREOENH, {done}MIXERCONTROL_CONTROLTYPE_BOOLEAN, {done}MIXERCONTROL_CONTROLTYPE_BUTTON, {done}MIXERCONTROL_CONTROLTYPE_LOUDNESS, {done}MIXERCONTROL_CONTROLTYPE_MONO, {done}MIXERCONTROL_CONTROLTYPE_MUTE : with fCBList do for i := 0 to Count - 1 do with tCheckBox(Items[i]) do Checked := fControl.Details_Boolean[Tag, 0]; // time MIXERCONTROL_CONTROLTYPE_MICROTIME : ; MIXERCONTROL_CONTROLTYPE_MILLITIME : ; else ; end; end; function NoNull(const aStr1, aStr2: string): string; begin if (aStr1 = '') then Result := aStr2 else Result := aStr1; end; procedure TvcMixerControl.RecreateControl; var i: Integer; c: Integer; lTB : tTrackBar; lPB : tProgressBar; lC : Integer; lBtn: tButton; lPM : tPopupMenu; lMI : tMenuItem; lCB : tCheckBox; lS : string; begin ClearLists; if ((fControl.ControlCaps.fdwControl and MIXERCONTROL_CONTROLF_MULTIPLE) <> 0) then lC := 0 else lC := fControl.ControlDetails.cChannels - 1; case fControl.ControlCaps.dwControlType of // custom {done}MIXERCONTROL_CONTROLTYPE_CUSTOM: fControl.OwnerHandle := fMaster.Handle; // fader {done}MIXERCONTROL_CONTROLTYPE_BASS, {done}MIXERCONTROL_CONTROLTYPE_TREBLE, {done}MIXERCONTROL_CONTROLTYPE_FADER, {done}MIXERCONTROL_CONTROLTYPE_VOLUME: begin for i := 0 to lC do begin lTB := TTrackBar.Create(fMaster); with lTB do begin Orientation := trVertical; TickStyle := tsNone; TickMarks := tmBoth; {$IFDEF VER110} {$ELSE} ThumbLength := 12; {$ENDIF} Top := 16; Left := fMaster.fLeftSide; Width := 16; Height := 110; LineSize := 1024; PageSize := 4096; Min := fControl.ControlCaps.Bounds.dwMinimum; Max := fControl.ControlCaps.Bounds.dwMaximum; OnChange := MyOnTrackChange; Tag := i; ShowHint := True; Hint := fControl.ControlCaps.szName; fMaster.AddLeft(Width + 1); end; fMaster.InsertControl(lTB); fTBList.Add(lTB); end; end; MIXERCONTROL_CONTROLTYPE_EQUALIZER : ; // list {done}MIXERCONTROL_CONTROLTYPE_MIXER, {done}MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT, {done}MIXERCONTROL_CONTROLTYPE_MUX, {done}MIXERCONTROL_CONTROLTYPE_SINGLESELECT: begin for i := 0 to lC do begin lBtn := tButton.Create(fMaster); with lBtn do begin Top := fMaster.fTopSide; Left := 2; Height := 16; Width := fMaster.Width - 4; Tag := i; Caption := fControl.ControlCaps.szShortName; //lBtn.Caption := fControl.ControlCaps.szShortName; ShowHint := True; Hint := fControl.ControlCaps.szName; OnClick := MyOnMuxButtonCLick; end; fMaster.InsertControl(lBtn); lPM := tPopupMenu.Create(fMaster); if Assigned(fControl.ControlDetails.paDetails) then ; // add long description lMI := tMenuItem.Create(lPM); lMI.Caption := fControl.ControlCaps.szName; lMI.Enabled := False; lPM.Items.Add(lMI); // and separator lMI := tMenuItem.Create(lPM); lMI.Caption := '-'; lPM.Items.Add(lMI); // and valid items for c := 0 to fControl.ListTextItems.Count - 1 do begin lMI := tMenuItem.Create(lPM); with lMI do begin Caption := fControl.ListTextItems[c]; case fControl.ControlCaps.dwControlType of MIXERCONTROL_CONTROLTYPE_MIXER, MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT: OnClick := MyOnMixPMClick; MIXERCONTROL_CONTROLTYPE_MUX, MIXERCONTROL_CONTROLTYPE_SINGLESELECT : OnClick := MyOnMuxPMClick; end; Tag := c; end; lPM.Items.Add(lMI); end; lBtn.PopupMenu := lPM; fMBList.Add(lBtn); Inc(fMaster.fTopSide, lBtn.Height + 2); end; end; // meter {done?}MIXERCONTROL_CONTROLTYPE_BOOLEANMETER, {done}MIXERCONTROL_CONTROLTYPE_SIGNEDMETER, {done}MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER, {done}MIXERCONTROL_CONTROLTYPE_PEAKMETER : begin for i := 0 to lC do begin lPB := tProgressBar.Create(fMaster); with lPB do begin {$IFDEF VER110} {$ELSE} Orientation := pbVertical; {$ENDIF} Width := 10; Height := 100; Left := fMaster.fLeftSide; Top := 21; Tag := i; ShowHint := True; Hint := fControl.ControlCaps.szName; //Smooth := True; case fControl.ControlCaps.dwControlType of MIXERCONTROL_CONTROLTYPE_PEAKMETER, MIXERCONTROL_CONTROLTYPE_SIGNEDMETER: begin Min := fControl.ControlCaps.Bounds.lMinimum; Max := fControl.ControlCaps.Bounds.lMaximum; end; MIXERCONTROL_CONTROLTYPE_BOOLEANMETER, MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER: begin Min := fControl.ControlCaps.Bounds.dwMinimum; Max := fControl.ControlCaps.Bounds.dwMaximum; end; end; fMaster.AddLeft(Width + 2); end; fMaster.InsertControl(lPB); fPBList.Add(lPB); end; //fMaster.fThread.Resume; end; // number MIXERCONTROL_CONTROLTYPE_DECIBELS : ; MIXERCONTROL_CONTROLTYPE_PERCENT : ; MIXERCONTROL_CONTROLTYPE_SIGNED : ; MIXERCONTROL_CONTROLTYPE_UNSIGNED : ; // slider MIXERCONTROL_CONTROLTYPE_PAN : ; MIXERCONTROL_CONTROLTYPE_QSOUNDPAN : ; MIXERCONTROL_CONTROLTYPE_SLIDER : ; // switch {done}MIXERCONTROL_CONTROLTYPE_STEREOENH, {done}MIXERCONTROL_CONTROLTYPE_ONOFF, {done}MIXERCONTROL_CONTROLTYPE_BOOLEAN, {done}MIXERCONTROL_CONTROLTYPE_BUTTON, {done}MIXERCONTROL_CONTROLTYPE_LOUDNESS, {done}MIXERCONTROL_CONTROLTYPE_MONO, {done}MIXERCONTROL_CONTROLTYPE_MUTE : begin for i := 0 to lC do begin lCB := tCheckBox.Create(fMaster); with lCB do begin lS := fControl.ControlCaps.szShortName; case fControl.ControlCaps.dwControlType of MIXERCONTROL_CONTROLTYPE_STEREOENH : lS := NoNull(lS, 'Stereo Enh.'); MIXERCONTROL_CONTROLTYPE_ONOFF : lS := NoNull(lS, 'On/Off'); MIXERCONTROL_CONTROLTYPE_BOOLEAN : lS := NoNull(lS, 'True/False'); MIXERCONTROL_CONTROLTYPE_BUTTON : lS := NoNull(lS, 'Button'); MIXERCONTROL_CONTROLTYPE_LOUDNESS : lS := NoNull(lS, 'Loudness'); MIXERCONTROL_CONTROLTYPE_MONO : lS := NoNull(lS, 'Mono'); MIXERCONTROL_CONTROLTYPE_MUTE : lS := 'Mute'; else lS := 'On/Off'; end; lCB.Caption := lS; Top := fMaster.fTopSide; Left := 1; Width := fMaster.Width - 2; OnClick := MyOnCBClick; ShowHint := True; Hint := fControl.ControlCaps.szName; Inc(fMaster.fTopSide, Height); end; fMaster.InsertControl(lCB); fCBList.Add(lCB); end; end; // time MIXERCONTROL_CONTROLTYPE_MICROTIME: ; MIXERCONTROL_CONTROLTYPE_MILLITIME: ; else ; end; OnChange; end; { TvcMixerLine } procedure TvcMixerLine.AddLeft(aDelta: Integer); begin Inc(fLeftSide, aDelta); if (Width < fLeftSide) then Width := fLeftSide; end; constructor TvcMixerLine.CreateMixerLine(aMixer: tvcMixer; aLine: tMsMixerLine); var i: Integer; lNTC: Boolean; begin inherited Create(aMixer); Width := Mixer.LineWidth; Height := 100; Color := (Owner as tvcMixer).LineColor; fLine := aLine; lNTC := False; fLeftSide := 1; fTopSide := 124; with fLine do begin EnumControls; for i := 0 to ControlCount - 1 do begin tvcMixerControl.CreateMixerControl(Self, ControlByIndex[i]); if ControlByIndex[i].IsPeakMeterControl then lNTC := True; end; end; if lNTC then begin fThread := tMixerLineMeterThread.Create(Self); fThread.Resume; end; end; procedure TvcMixerLine.RecreateControls; var i: Integer; lCap: tLabel; begin for i := 0 to ComponentCount - 1 do if (Components[i] is tvcMixerControl) then with (Components[i] as tvcMixerControl) do RecreateControl; lCap := tLabel.Create(Self); with lCap do begin lCap.Caption := fLine.LineCaps.szShortName; Left := 4; Top := 2; end; InsertControl(lCap); end; destructor TvcMixerLine.Destroy; begin if Assigned(fThread) then with fThread do begin Terminate; if Suspended then Resume; WaitFor; Free; end; inherited; end; function TvcMixerLine.GetMixer: tvcMixer; begin Result := (Owner as tvcMixer); end; procedure TvcMixerLine.OnChange(FromThread: Boolean{ = False}); var i: Integer; begin if FromThread then for i := 0 to ComponentCount - 1 do if (Components[i] is tvcMixerControl) then with (Components[i] as tvcMixerControl) do begin if fControl.IsPeakMeterControl then OnChange; end; end; { TvcMixer } procedure TvcMixer.Close; begin Active := False; end; constructor TvcMixer.Create(Owner: tComponent); begin inherited Create(Owner); fMixer := tMsMixerSystem.Create(nil); fLineColor := clBtnFace; fPMScale := 1; fPMShift := 3; fLineWidth := 70; end; destructor TvcMixer.Destroy; begin Close; fMixer.Free; inherited; end; procedure TvcMixer.DoClose; var i: Integer; begin for i := 0 to fMixer.MixerCount - 1 do fMixer[i].Close; end; procedure TvcMixer.DoControlChange(aMixerID, aControlID: Cardinal); var i: Integer; j: Integer; begin for i := 0 to ControlCount - 1 do if (Controls[i] is tvcMixerLine) then with (Controls[i] as tvcMixerLine) do for j := 0 to ComponentCount - 1 do if (Components[j] is tvcMixerControl) then with (Components[j] as tvcMixerControl) do if (fControl.ControlCaps.dwControlID = aControlID) then OnChange; end; procedure TvcMixer.DoLineChange(aMixerID, aLineID: Cardinal); var i: Integer; begin for i := 0 to ControlCount - 1 do if (Controls[i] is tvcMixerLine) then with (Controls[i] as tvcMixerLine) do if (fLine.LineCaps.dwLineID = aLineID) then OnChange(False); end; procedure TvcMixer.DoOpen; procedure InsertBevel; var lB: tBevel; begin lB := tBevel.Create(Self); with lB do begin Width := 2; Align := alLeft; Left := 1001; end; InsertControl(lB); end; procedure InsertLine(aLine: tMsMixerLine); var lC : tvcMixerLine; begin if fShowDL or ((aLine.LineCaps.fdwLine and MIXERLINE_LINEF_DISCONNECTED) = 0) then begin lC := tvcMixerLine.CreateMixerLine(Self, aLine); with lC do begin Align := alLeft; Left := 1000; end; InsertControl(lC); lC.RecreateControls; InsertBevel; end; end; var i: Integer; l: Integer; k: Integer; lD : Cardinal; lOK: Boolean; begin DestroyComponents; i := MixerIndex; if (i < 0) then i := 0; if (i >= Integer(fMixer.MixerCount)) then i := fMixer.MixerCount - 1; with fMixer[i] do begin WinHandle := Handle; Open; if Active then begin EnumLines; for l := 0 to LineCount - 1 do with fMixer[i][l] do begin lOK := False; lD := 0; case LinesDestMode of ldPlayback : lD := 0; ldRecording: lD := 1; ldCustom : lD := fLineDest; else lOK := True; end; if not lOK then lOK := (lD = LineCaps.dwDestination); if lOK then begin InsertLine(fMixer[i][l]); EnumSourceLines; for k := SourceLineCount - 1 downto 0 do InsertLine(fMixer[i][l][k]); end; end; end; end; end; procedure TvcMixer.MMControlChange(var Msg: tMMControlChangeEvent); begin DoControlChange(Msg.rMixer, Msg.rControlID); end; procedure TvcMixer.MMLineChange(var Msg: tMMLineChangeEvent); begin DoLineChange(Msg.rMixer, Msg.rLineID); end; procedure TvcMixer.Open; begin Active := True; end; procedure TvcMixer.SetActive(const Value: Boolean); begin if (fActive <> Value) then begin fActive := Value; if Value then DoOpen else DoClose; end; end; end.