Subversion Repositories spacemission

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. {
  2.  SXMedia  Components - Beta 1
  3.  --------------------------------
  4.  Copyright 1999 Dean Ellis
  5.  http://www.sillex.freeserve.co.uk
  6.  
  7.  This unit is part of the SXMedia Component Set. This code is
  8.  supplied as is with no guarantees and must be used at your own
  9.  risk.
  10.  
  11.  No modifications to this code must be made without the express
  12.  permission of the author. Please report any problems to
  13.  support@sillex.freeserve.co.uk
  14.  
  15.  You may use these components to create any freeware/shareware
  16.  applications that you wish. If the components are to be used in
  17.  a commercail product then credit for developement of these components
  18.  should be given.
  19.  
  20.  Credits :
  21.  
  22.  Developer : Dean Ellis
  23.  Testers   : Dominique Louis
  24.              Ivan Blecic
  25.              Naoki Haga
  26.  
  27. Version History
  28. --------------------------------------------------------------------------------
  29. 25/01/2000 Modified Destroy and Stop methods to check IDSBuffer before
  30.            calling IBuffer.Stop. Stops the "Buffer not Created exception"
  31.            being thrown.
  32. 06/02/2000 Added OnStop and OnStart events
  33.            Added Finalize method. This does not have to be called but if you
  34.            manually finalize and initialize the DXSound Component you
  35.            need to call this method to clear the buffers and the Threads.
  36. 22/02/2000 Modified SetLoop code to correct functionality.
  37.            Modifed Stop method to make sure the OnStop event is only called
  38.            if it wsa playing and that the Value of Playing would be False
  39.            when the event is fired.
  40. 15/05/2000 Made use of the MppSdkLibLoaded variable to make sure the component
  41.            does nothing if the mppsdk.dll is not found.
  42.            Removed the raising of an exception in the constructor as it seemed to
  43.            be causing more problems than it was solving.
  44. --------------------------------------------------------------------------------
  45. }
  46. unit SXModPlayer;
  47.  
  48. {$INCLUDE DelphiXcfg.inc}
  49.  
  50. interface
  51.  
  52. uses
  53.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  54.   DXSounds, MMSystem, MpSndSys,
  55.   {$IFDEF StandardDX}
  56.   DirectDraw, DirectSound,
  57.     {$IFDEF DX7}
  58.       {$IFDEF D3DRM}
  59.   Direct3DRM,
  60.       {$ENDIF}
  61.   Direct3D;
  62.     {$ENDIF}
  63.     {$IFDEF DX9}
  64.   Direct3D9, Direct3D, D3DX9, {Direct3D8,} DX7toDX8;
  65.     {$ENDIF}
  66.   {$ELSE}
  67.   DirectX;
  68.   {$ENDIF}
  69.  
  70. type
  71.  
  72.    TModOption = (NoResampling, BassExpansion, Surround, Reverb,
  73.                   HighQuality, GainControl, NoiseReduction);
  74.  
  75.    TModOptions = Set of TModOption;
  76.  
  77.    TSXModPlayer = class(TComponent)
  78.      private
  79.        { Private declarations }
  80.        FFilename: TFilename;
  81.        FDXSound : TDXSound;
  82.        FSoundBuffer : TDirectSoundBuffer;
  83.        FSoundNotify : IDirectSoundNotify;
  84.        FBufferDesc : TDSBufferDesc;
  85.        FWaveFormat : TWaveFormatEx;
  86.        FLoop : Boolean;
  87.        FPlaying : Boolean;
  88.        FOptions : TModOptions;
  89.        FEvents: TList;
  90.        FNotify: TList;
  91.        FOnStop: TNotifyEvent;
  92.        FOnStart: TNotifyEvent;
  93.        function GetPosition : Integer;
  94.        function GetSilenceData:Integer;
  95.      protected
  96.        { Protected declarations }
  97.        procedure InitSoundEvents;
  98.        procedure ThreadCallback;
  99.        procedure SetFilename(const Value : TFilename);
  100.        procedure SetLoop(const Value : Boolean);
  101.        procedure SetOptions(const Value : TModOptions);
  102.        procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  103.        procedure DoStop;
  104.        procedure DoStart;
  105.  
  106.        function CreateEventList : TList;
  107.        function CreateNotifyList : TList;
  108.        function CreateSoundBuffer : TDirectSoundBuffer;
  109.        function CreateSoundNotify : IDirectSoundNotify;
  110.        procedure ClearSoundNotify;
  111.        procedure CreateEventThread;
  112.        procedure ClearSoundBuffer;
  113.  
  114.        procedure StartThread;
  115.        procedure StopThread;
  116.  
  117.        property BufferDesc : TDSBufferDesc read FBufferDesc write FBufferDesc;
  118.        property WaveFormat : TWaveFormatEx read FWaveFormat write FWaveFormat;
  119.        property SoundBuffer : TDirectSoundBuffer read FSoundBuffer write FSoundBuffer;
  120.        property SoundNotify : IDirectSoundNotify read FSoundNotify write FSoundNotify;
  121.        property Events : TList read FEvents write FEvents;
  122.        property Notify : TList read FNotify write FNotify;
  123.      public
  124.        { Public declarations }
  125.        constructor Create( AOwner : TComponent); override;
  126.        destructor Destroy; override;
  127.        procedure Initialize(Stream:TMemoryStream);
  128.        procedure Finalize;
  129.        procedure Play(Loop : Boolean);
  130.        procedure Stop;
  131.        procedure Reset;
  132.  
  133.        property Position : Integer read GetPosition;
  134.        property Playing : Boolean read FPlaying;
  135.      published
  136.        { Published declarations }
  137.        property Filename : TFilename read FFilename write SetFilename;
  138.        property DXSound : TDXSound read FDXSound write FDXSound;
  139.        property Looping : Boolean read FLoop write SetLoop default False;
  140.        property Options : TModOptions read FOptions write SetOptions;
  141.        property OnStart : TNotifyEvent read FOnStart write FOnStart;
  142.        property OnStop : TNotifyEvent read FOnStop write FOnStop;
  143.   end;
  144.  
  145. implementation
  146.  
  147. // If you are having problems compiling the package edit this file.
  148. // Comment out this include Statement. You should only need to do this
  149. // if you are NOT using the latest version of DelphiX (991024)
  150. // Comment this Define out if you are using DelphiX992404 or earlier
  151. {$DEFINE VERSION991024}
  152.  
  153. const
  154.     EVENTCOUNT:Integer = 2;
  155.  
  156. type
  157.     {Music Event Callback thread}
  158.     TSXEventThread = class(TThread)
  159.        private
  160.           FEventCallback:TThreadMethod;
  161.        public
  162.           procedure Execute; override;
  163.           property EventCallback : TThreadMethod read FEventCallback write FEventCallback;
  164.  
  165.     end;
  166.  
  167.  
  168. var
  169.  
  170.    EventThread : TSXEventThread;
  171.    CurrentEvent : Integer;
  172.  
  173. { TSXEventThread }
  174.  
  175. procedure TSXEventThread.Execute;
  176. begin
  177.   if not Assigned(EventCallback) then Terminate;
  178.   while not Terminated do
  179.   begin
  180.      EventCallback;
  181.   end;
  182. end;
  183.  
  184. { TSXModPlayer }
  185.  
  186. constructor TSXModPlayer.Create(AOwner : TComponent);
  187. begin
  188.    inherited Create(AOwner);
  189.    if MppSdkLibLoaded then
  190.      ModMixer.SetMixerOptions(0);
  191.    Events := CreateEventList;
  192.    Notify := CreateNotifyList;
  193. end;
  194. destructor TSXModPlayer.Destroy;
  195. begin
  196.    Finalize;
  197.    {}
  198.    Notify.Free;
  199.    Events.Free;
  200.    inherited Destroy;
  201. end;
  202. procedure TSXModPlayer.Initialize(Stream:TMemoryStream);
  203. var Data:Pointer;
  204.     Size:Integer;
  205.     FreeStream:Boolean;
  206. begin
  207.   try
  208.      if MppSdkLibLoaded then
  209.      begin
  210.        if SoundBuffer = nil then InitSoundEvents;
  211.        FreeStream := False;
  212.        if Stream = nil then
  213.        begin
  214.           Stream := TMemoryStream.Create;
  215.           Stream.LoadFromFile(Filename);
  216.           FreeStream := True;
  217.        end;
  218.        Data := Stream.Memory;
  219.        Size := Stream.Size;
  220.        ModMixer.FreeSong;
  221.        ModMixer.LoadSong(Data,Size);
  222.        if FreeStream then Stream.Free;
  223.      end;
  224.   except
  225.   end;
  226. end;
  227. procedure TSXModPlayer.Finalize;
  228. begin
  229.    Stop;
  230.    if Assigned(EventThread) then
  231.    begin
  232.       EventThread.Terminate;
  233.       EventThread.Free;
  234.       EventThread := nil;
  235.    end;
  236.    ClearSoundNotify;
  237.    if Assigned(SoundBuffer) then
  238.    begin
  239.       try
  240.          if Playing and (SoundBuffer.IDSBuffer <> nil) then
  241.             SoundBuffer.IBuffer.Stop;
  242.       finally
  243.          SoundBuffer := nil;
  244.       end;
  245.    end;
  246. end;
  247.  
  248. procedure TSXModPlayer.InitSoundEvents;
  249. // DelphiX Version 991024 Edit Version.inc to change declarations
  250. {$IFDEF VERSION991024}
  251.   var SizeWritten: Cardinal;
  252. {$ELSE}
  253. // DelphiX Version 992404 and earlier
  254.   var  SizeWritten : Integer;
  255. {$ENDIF}
  256. begin
  257.    if MppSdkLibLoaded and Assigned(DXSound) then
  258.    begin
  259.       DXSound.Primary.IBuffer.GetFormat(@FWaveFormat,Sizeof(WaveFormat),@SizeWritten);
  260.       ModMixer.SetWaveFormat(WaveFormat.nSamplesPerSec,WaveFormat.nChannels,WaveFormat.wBitsPerSample);
  261.       SoundBuffer := CreateSoundBuffer;
  262.       SoundNotify := CreateSoundNotify;
  263.       if (SoundBuffer <> nil) and (SoundNotify <> nil) then
  264.          CreateEventThread;
  265.    end;
  266. end;
  267. procedure TSXModPlayer.ThreadCallback;
  268. var Msg : TMsg;
  269.  
  270.    procedure ReadData(Event:Integer);
  271.    var W1:pointer;
  272.        // DelphiX Version 991024 Edit Version.inc to change declarations
  273.        {$IFDEF VERSION991024}
  274.        S1, S2 : Cardinal;
  275.        {$ELSE}
  276.        // DelphiX Version 992404 and earlier
  277.        S1,S2:Integer;
  278.        {$ENDIF}
  279.        NumWrite:Longint;
  280.        Pos:Integer;
  281.        Written1:Integer;
  282.    begin
  283.      if Event = 0 then
  284.         Pos := TDSBPositionNotify(Notify[EVENTCOUNT -1]^).dwOffset
  285.      else
  286.         Pos := TDSBPositionNotify(Notify[Event-1]^).dwOffset;
  287.      NumWrite := TDSBPositionNotify(Notify[Event]^).dwOffset - Pos;
  288.      if (NumWrite < 0) then
  289.         inc(NumWrite,BufferDesc.dwBufferBytes);
  290.      if SoundBuffer.IBuffer.Lock(Pos,NumWrite,w1,{$IFDEF UNICODE}@{$ENDIF}s1,Pointer(nil^),{$IFDEF UNICODE}@{$ENDIF}s2,0) = 0 then
  291.      begin
  292.         Written1 := ModMixer.Render(W1,S1);
  293.         SoundBuffer.IBuffer.Unlock(w1,Written1, nil,0);
  294.         if (Written1 = 0) then
  295.            Stop;
  296.      end;
  297.    end;
  298.  
  299. begin
  300.     CurrentEvent := MsgWaitForMultipleObjects(EVENTCOUNT,Events.List{$IFNDEF UNICODE}^{$ENDIF}[0], False, INFINITE, QS_ALLINPUT);
  301.     dec(CurrentEvent,WAIT_OBJECT_0);
  302.     if CurrentEvent >= EVENTCOUNT then
  303.     begin
  304.       while (PeekMessage(Msg, 0, 0,0, PM_REMOVE)) do
  305.       begin
  306.         if Msg.Message = WM_QUIT then
  307.            Stop
  308.         else
  309.         begin
  310.           TranslateMessage(Msg);
  311.           DispatchMessage(Msg);
  312.         end;
  313.       end;
  314.     end
  315.     else
  316.     begin
  317.        ReadData(CurrentEvent);
  318.     end;
  319. end;
  320. function TSXModPlayer.CreateEventList : TList;
  321. begin
  322.   Result := TList.Create;
  323.   Result.Capacity := EVENTCOUNT;
  324. end;
  325. function TSXModPlayer.CreateNotifyList : TList;
  326. begin
  327.   Result := TList.Create;
  328.   Result.Capacity := EVENTCOUNT;
  329. end;
  330. function TSXModPlayer.CreateSoundBuffer : TDirectSoundBuffer;
  331. {$IFDEF UNICODE}
  332. const DSBCAPS_CTRLDEFAULT = DSBCAPS_CTRLPAN or DSBCAPS_CTRLVOLUME or DSBCAPS_CTRLFREQUENCY;
  333. {$ENDIF}
  334. begin
  335.    Result := TDirectSoundBuffer.Create(DXSound.DSound);
  336.    ZeroMemory(@BufferDesc,Sizeof(TDSBufferDesc));
  337.    FBufferDesc.dwSize := Sizeof(TDSBufferDesc);
  338.    FBufferDesc.dwFlags := DSBCAPS_CTRLDEFAULT or DSBCAPS_STATIC or
  339.                          DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_CTRLPOSITIONNOTIFY;
  340.    FBufferDesc.dwBufferBytes := WaveFormat.nAvgBytesPerSec * 2;
  341.    FBufferDesc.lpwfxFormat := @WaveFormat;
  342.    Result.CreateBuffer(BufferDesc);
  343. end;
  344. function TSXModPlayer.CreateSoundNotify : IDirectSoundNotify;
  345. type TNotifyArray = Array[0..99] of TDSBPositionNotify;
  346. var PDSNotify : PDSBPositionNotify;
  347.     index : Integer; Offset: Integer;
  348.     PNotify: ^TNotifyArray;
  349. begin
  350.    Result := nil;
  351.    if SoundBuffer.IBuffer.QueryInterface(IID_IDirectSoundNotify,Result) = 0 then
  352.    begin
  353.       {setup notifications here}
  354.       Offset := 0;
  355.       GetMem(PNotify,EVENTCOUNT * Sizeof(TDSBPositionNotify));
  356.       for Index := 1 to EVENTCOUNT do
  357.       begin
  358.          New(PDSNotify);
  359.          PDSNotify^.dwOffset := OffSet;
  360.          PDSNotify^.hEventNotify := CreateEvent(nil,False,False,nil);
  361.          Notify.Add(PDSNotify);
  362.          Events.Add(Pointer(PDSNotify^.hEventNotify));
  363.          PNotify[Index-1] := PDSNotify^;
  364.          inc(Offset,WaveFormat.nAvgBytesPerSec div EVENTCOUNT);
  365.       end;
  366.  
  367.       if Result.SetNotificationPositions(EVENTCOUNT,{$IFDEF UNICODE}@{$ENDIF}PNotify[0]) <> 0 then
  368.          ShowMessage('Notification Falied');
  369.       FreeMem(PNotify,EVENTCOUNT * Sizeof(TDSBPositionNotify));
  370.    end;
  371. end;
  372. procedure TSXModPlayer.ClearSoundNotify;
  373. var PDSNotify : PDSBPositionNotify;
  374.     Index : Integer;
  375. begin
  376.    for Index := Notify.Count -1 downto 0 do
  377.    begin
  378.       PDSNotify := Notify[Index];
  379.       Notify.Delete(Index);
  380.       Events.Delete(Index);
  381.       CloseHandle(PDSNotify^.hEventNotify);
  382.    end;
  383.    SoundNotify := nil;
  384. end;
  385. procedure TSXModPlayer.CreateEventThread;
  386. begin
  387.   if not Assigned(EventThread) then
  388.   begin
  389.      EventThread := TSXEventThread.Create(True);
  390.      EventThread.Priority := tpNormal;
  391.      EventThread.EventCallback := ThreadCallback;
  392.   end;
  393. end;
  394.  
  395. procedure TSXModPlayer.ClearSoundBuffer;
  396. var w1,w2:pointer;
  397.     // DelphiX Version 991024 Edit Version.inc to change declarations
  398.     {$IFDEF VERSION991024}
  399.     S1, S2 : Cardinal;
  400.     {$ELSE}
  401.     // DelphiX Version 992404 and earlier
  402.     S1,S2:Integer;
  403.     {$ENDIF}
  404.     Data:Word;
  405. begin
  406.   Data := GetSilenceData;
  407.   if SoundBuffer.IBuffer.Lock(0,0,w1,{$IFDEF UNICODE}@{$ENDIF}s1,w2,{$IFDEF UNICODE}@{$ENDIF}s2,DSBLOCK_ENTIREBUFFER) = 0 then
  408.   begin
  409.      FillMemory(W1,S1,Data);
  410.      if W2 <> nil then
  411.         FillMemory(W2,S2,Data);
  412.      SoundBuffer.IBuffer.Unlock(W1,S1,W2,S2);
  413.   end;
  414. end;
  415. procedure TSXModPlayer.StartThread;
  416. begin
  417.   if Assigned(EventThread) then EventThread.Resume;
  418. end;
  419. procedure TSXModPlayer.StopThread;
  420. begin
  421.   if Assigned(EventThread) then EventThread.Suspend;
  422. end;
  423. {}
  424. procedure TSXModPlayer.Play(Loop : Boolean);
  425. begin
  426.    if MppSdkLibLoaded then
  427.    begin
  428.      if Assigned(SoundBuffer) and Assigned(EventThread) then
  429.      begin
  430.         SetLoop(Loop);
  431.         ClearSoundBuffer;
  432.         StartThread;
  433.         FPlaying := True;
  434.         DoStart;
  435.         SoundBuffer.IBuffer.Play(0,0,DSBPLAY_LOOPING);
  436.      end;
  437.    end;
  438. end;
  439. procedure TSXModPlayer.Stop;
  440. begin
  441.    if MppSdkLibLoaded then
  442.    begin
  443.      if Assigned(SoundBuffer) and Assigned(EventThread) then
  444.      begin
  445.         try
  446.           if Playing and (SoundBuffer.IDSBuffer <> nil) then
  447.           begin
  448.             FPlaying := False;
  449.             SoundBuffer.IBuffer.Stop;
  450.             DoStop;
  451.           end;
  452.         finally
  453.           StopThread;
  454.         end;
  455.      end;
  456.    end;
  457. end;
  458. procedure TSXModPlayer.Reset;
  459. begin
  460.    if MppSdkLibLoaded then
  461.      ModMixer.SetCurrentOrder(0);
  462. end;
  463. {Property Accessors}
  464. function TSXModPlayer.GetPosition : Integer;
  465. begin
  466.   Result := 0;
  467. end;
  468. procedure TSXModPlayer.SetFilename(const Value : TFilename);
  469. begin
  470.   if FFilename <> Value then
  471.   begin
  472.      FFilename := Value;
  473.   end;
  474. end;
  475. procedure TSXModPlayer.SetLoop( const Value : Boolean);
  476. var Flags : DWord;
  477. begin
  478.    if FLoop <> Value then
  479.    begin
  480.       FLoop := Value;
  481.       if MppSdkLibLoaded then
  482.       begin
  483.         Flags := ModMixer.GetMixerOptions;
  484.         case Value of
  485.            True:Flags := Flags or MPPMIX_LOOP;
  486.            False:Flags := Flags and (not MPPMIX_LOOP);
  487.         end;
  488.         ModMixer.SetMixerOptions(Flags);
  489.       end;
  490.    end;
  491. end;
  492. procedure TSXModPlayer.SetOptions( const Value : TModOptions );
  493. const OptionArray: array[Boolean,TModOption] of Integer = (
  494.     (0,0,0,0,0,0,0),
  495.    (MPPMIX_NORESAMPLING, MPPMIX_BASSEXPANSION,  MPPMIX_SURROUND,
  496.    MPPMIX_REVERB, MPPMIX_HIGHQUALITY, MPPMIX_GAINCONTROL,
  497.    MPPMIX_NOISEREDUCTION)
  498.    );
  499. var Flags : DWord;
  500. begin
  501.    if FOptions <> Value then
  502.    begin
  503.       FOptions := Value;
  504.       if MppSdkLibLoaded then
  505.       begin
  506.         Flags := 0;
  507.         Flags := Flags or OptionArray[NoResampling in Value,NoResampling];
  508.         Flags := Flags or OptionArray[BassExpansion in Value,BassExpansion];
  509.         Flags := Flags or OptionArray[Surround in Value,Surround];
  510.         Flags := Flags or OptionArray[Reverb in Value,Reverb];
  511.         Flags := Flags or OptionArray[HighQuality in Value,HighQuality];
  512.         Flags := Flags or OptionArray[GainControl in Value,GainControl];
  513.         Flags := Flags or OptionArray[NoiseReduction in Value,NoiseReduction];
  514.         ModMixer.SetMixerOptions(Flags);
  515.         SetLoop(Looping);
  516.       end;
  517.    end;
  518. end;
  519. procedure TSXModPlayer.Notification(AComponent: TComponent; Operation: TOperation);
  520. begin
  521.    inherited Notification( AComponent, Operation);
  522.  
  523.    if (Operation = opRemove) and (AComponent = DXSound) then
  524.       DXSound := nil;
  525. end;
  526. procedure TSXModPlayer.DoStop;
  527. begin
  528.   if Assigned(FOnStop) then
  529.     FOnStop(Self);
  530. end;
  531. procedure TSXModPlayer.DoStart;
  532. begin
  533.   if Assigned(FOnStart) then
  534.     FOnStart(Self);
  535. end;
  536. function TSXModPlayer.GetSilenceData:integer;
  537. const SilenceData:array[1..2] of integer = ($80,$0);
  538. begin
  539.   Result := SilenceData[WaveFormat.wBitsPerSample div 8];
  540. end;
  541.  
  542. end.
  543.