unit _WAOutputPlugin_Moonlight;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,_WAOutputPlugin;

{$WARN UNIT_PLATFORM OFF}
{$WARN SYMBOL_PLATFORM OFF}

type
  TModules=record
    Initialized:boolean;
    DLLName:string;
    DLLHandle:THandle;
    Module:^TOut_module;
    Openned:boolean;
  end;

type
  TWAOUT = class
  private
    { Private 宣言 }
    MemSync:TMultiReadExclusiveWriteSynchronizer;
    ModulesCount:integer;
    Modules:array of TModules;
    ActiveModuleIndex:integer;
    SafeTimeMS,PCMRate:dword;
    procedure freeDLL(idx:integer);
    function initDLL(idx:integer;DLL:string):boolean;
    function CheckModule(idx:integer):boolean;
  public
    { Public 宣言 }
    constructor Create;
    destructor Destroy; override;
    function GetModuleIndexFromName(DLL:string):integer;
    procedure initDLLs;
    procedure freeDLLs;
    function GetModuleInitialized(idx:integer):boolean;
    function SetActiveModule(DLL:string):boolean;
    function Play(_SafeTimeMS,_PCMRate:dword):boolean;
    procedure Stop;
    procedure ShowAbout(DLL:string);
    procedure ShowConfig(DLL:string);
    function GetCanWriteLength:integer;
    function SetBuffer(buf:pointer;len:integer):boolean;
    function GetDifferenceMS:integer;
    function GetModulesCount:integer;
    function GetModuleDLLName(idx:integer):string;
  end;

implementation

uses _const;

constructor TWAOUT.Create;
begin
  inherited Create;

  MemSync:=TMultiReadExclusiveWriteSynchronizer.Create;

  MemSync.BeginWrite;

  ActiveModuleIndex:=-1;
  ModulesCount:=0;
  SetLength(Modules,ModulesCount);

  SafeTimeMS:=0;
  PCMRate:=0;

  MemSync.EndWrite;
end;

destructor TWAOUT.Destroy;
begin
  MemSync.BeginWrite;

  ActiveModuleIndex:=-1;
  ModulesCount:=0;
  SetLength(Modules,ModulesCount);

  MemSync.EndWrite;

  MemSync.Free;

  inherited Destroy;
end;

function TWAOUT.GetModuleIndexFromName(DLL:string):integer;
var
  cnt:integer;
begin
  Result:=-1;
  for cnt:=0 to ModulesCount-1 do begin
    if AnsiLowerCase(DLL)=AnsiLowerCase(Modules[cnt].DLLName) then Result:=cnt;
  end;
end;

procedure TWAOUT.initDLLs;
var
  res:integer;
  SearchRec: TSearchRec;
begin
  ModulesCount:=0;
  SetLength(Modules,ModulesCount);

  res:=FindFirst(StartPath+'Plugins\out_*.dll', (faReadOnly or faHidden or faSysFile or faArchive), SearchRec);
  if res<>0 then begin
    end else begin
    repeat
      SetLength(Modules,ModulesCount+1);
      initDLL(ModulesCount,SearchRec.Name);
      inc(ModulesCount);
      res:=FindNext(SearchRec);
    until (res<>0);
  end;
  FindClose(SearchRec);
end;

function TWAOUT.initDLL(idx:integer;DLL:string):boolean;
var
  getModule:function:pointer;stdcall;
  DLLFullpath:string;
  function _InitDLL:Boolean;
  begin
    Result:=False;
    with Modules[idx] do begin
      if DLLHandle<>0 then begin
        ShowMessage('DLLは既に読み込まれています。');
        Result:=False;
        exit;
      end;
      getModule:=nil;
      DLLHandle:=LoadLibrary(PChar(DLLFullpath));
      if DLLHandle=0 then exit;
      getModule:=GetProcAddress(DLLHandle,'winampGetOutModule');
      if @getModule<>nil then Result:=True;
    end;
  end;
begin
  DLLFullpath:=StartPath+'Plugins\'+DLL;

  Result:=_InitDLL;
  with Modules[idx] do begin
    if Result=False then begin
      Initialized:=False;
      end else begin
      Openned:=False;
      DLLName:=DLL;
      Module:=getModule;
      Module.hMainWindow:=Application.Handle;
      Module.hDllInstance:=FindHInstance(getModule);
      Module.init;
      Module.SetPan(0);
      Module.setvolume($ff);
      Initialized:=True;
    end;
  end;
end;

procedure TWAOUT.freeDLL(idx:integer);
begin
  with Modules[idx] do begin
    Openned:=False;
    if assigned(Module) then begin
      MemSync.BeginWrite;
      Module.Quit;
      Module:=nil;
      MemSync.EndWrite;
    end;
    if DLLHandle<>0 then begin
      FreeLibrary(DLLHandle);
      DLLHandle:=0;
    end;
  end;
end;

procedure TWAOUT.freeDLLs;
var
  idx:integer;
begin
  Stop;

  for idx:=0 to ModulesCount-1 do begin
    freeDLL(idx);
  end;
end;

function TWAOUT.GetModuleInitialized(idx:integer):boolean;
begin
  Result:=Modules[idx].Initialized;
end;

function TWAOUT.SetActiveModule(DLL:string):boolean;
var
  idx:integer;
begin
  Result:=False;

  Stop;
  idx:=GetModuleIndexFromName(DLL);
  if idx=-1 then begin
    ShowMessage(DLL+'名を持つプラグインが見つかりませんでした。');
    exit;
  end;

  if Modules[idx].Initialized=False then begin
    ShowMessage('プラグイン初期化エラーが発生しました。');
    exit;
  end;

  ActiveModuleIndex:=idx;
  Result:=True;
end;

function TWAOUT.CheckModule(idx:integer):boolean;
begin
  Result:=False;
  if idx=-1 then exit;
  if (idx<0) or (ModulesCount<=idx) then exit;
  if assigned(Modules[idx].Module)=False then exit;
  if Modules[idx].Initialized=False then exit;
  Result:=True;
end;

function TWAOUT.Play(_SafeTimeMS,_PCMRate:dword):boolean;
begin
  if _SafeTimeMS<>0 then SafeTimeMS:=_SafeTimeMS;
  if _PCMRate<>0 then PCMRate:=_PCMRate;

  if CheckModule(ActiveModuleIndex)=False then begin
    Result:=False;
    exit;
  end;

  with Modules[ActiveModuleIndex] do begin
    if Openned=True then begin
      ShowMessage('WAOUT_Play:音声出力デバイスは既に開かれています。');
      Result:=False;
      end else begin
      if Module.Open(PCMRate,2,16,SafeTimeMS,0)>=0 then begin
        Openned:=True;
        Result:=True;
        end else begin
        ShowMessage('WAOUT_Play:音声出力デバイスが開けませんでした。');
        Openned:=False;
        Result:=False;
      end;
    end;
  end;
end;

procedure TWAOUT.Stop;
begin
  if ActiveModuleIndex=-1 then exit;
  if CheckModule(ActiveModuleIndex)=False then exit;

  with Modules[ActiveModuleIndex] do begin
    if Openned=True then begin
      Openned:=False;
      Module.Close;
    end;
  end;
end;

procedure TWAOUT.ShowAbout(DLL:string);
var
  idx:integer;
begin
  if DLL='' then begin
    idx:=ActiveModuleIndex;
    end else begin
    idx:=GetModuleIndexFromName(DLL);
  end;
  if CheckModule(idx)=False then exit;

  if assigned(Modules[idx].Module)=False then begin
    ShowMessage('WAOUT_ShowAbout:WinAMP OutputPluginが読み込まれていません。');
    exit;
  end;

  Modules[idx].Module.About(Application.Handle);
end;

procedure TWAOUT.ShowConfig(DLL:string);
var
  idx:integer;
begin
  if DLL='' then begin
    idx:=ActiveModuleIndex;
    end else begin
    idx:=GetModuleIndexFromName(DLL);
  end;
  if CheckModule(idx)=False then exit;

  if assigned(Modules[idx].Module)=False then begin
    ShowMessage('WAOUT_ShowConfig:WinAMP OutputPluginが読み込まれていません。');
    exit;
  end;

  Modules[idx].Module.Config(Application.Handle);
  Stop;
  Play(0,0);
end;

function TWAOUT.GetCanWriteLength:integer;
begin
  Result:=0;
  if CheckModule(ActiveModuleIndex)=False then exit;
  if Modules[ActiveModuleIndex].Openned=False then exit;

  Result:=Modules[ActiveModuleIndex].Module.CanWrite;
end;

function TWAOUT.SetBuffer(buf:pointer;len:integer):boolean;
begin
  Result:=False;
  if CheckModule(ActiveModuleIndex)=False then exit;
  if Modules[ActiveModuleIndex].Openned=False then exit;

  if Modules[ActiveModuleIndex].Module.Write(buf,len)=0 then begin
    Result:=True;
    end else begin
    Result:=False;
  end;
end;

function TWAOUT.GetDifferenceMS:integer;
begin
  Result:=0;
  if CheckModule(ActiveModuleIndex)=False then exit;
  if Modules[ActiveModuleIndex].Openned=False then exit;

  Result:=Modules[ActiveModuleIndex].Module.GetWrittenTime-Modules[ActiveModuleIndex].Module.GetOutputTime;
end;

function TWAOUT.GetModulesCount:integer;
begin
  Result:=ModulesCount;
end;

function TWAOUT.GetModuleDLLName(idx:integer):string;
begin
  if idx=-1 then begin
    Result:='';
    exit;
  end;

  Result:=Modules[idx].DLLName;
end;

end.
