• 在 Delphi 下使用 DirectSound (9): 效果器初步及 IDirectSoundFXGargle8 效果器



    只有使用 IDirectSoundBuffer8 的次缓冲区才能设置"特效", 主缓冲区主要负责的是混音和处理 3D 效果.

    IDirectSoundBuffer8(非 IDirectSoundBuffer) 支持以下效果器:
    IDirectSoundFXChorus8      //合唱; 微调原生与回声的延迟
    IDirectSoundFXCompressor8  //压缩; 压缩某些振幅
    IDirectSoundFXDistortion8  //失真; 将波形顶部修改为方形或锯齿形
    IDirectSoundFXEcho8        //回声; 重复并衰减回声
    IDirectSoundFXFlanger8     //镶边; 延迟回声
    IDirectSoundFXGargle8      //漱口; 有人叫它咕嘟效果
    IDirectSoundFXI3DL2Reverb8 //环境混响; 房间、大厅等
    IDirectSoundFXParamEq8     //均衡; 缩放不同频率的信号
    IDirectSoundFXWavesReverb8 //混响; DirectX Media Objects(DMOs), 是微软从 Waves 购买的声音处理技术 
    


    使用步骤:
    1、通过 IDirectSoundBuffer8 的 SetFX() 方法关联特效, 这个过程主要是给 SetFX() 方法的参数准备 TDSEffectDesc 结构数组;
    2、通过 IDirectSoundBuffer8 的 GetObjectInPath() 方法获取特效对象;
    3、通过特效对象的 SetAllParameters() 方法设置特效参数.

    9 个特效对象都只有两个方法: GetAllParameters()、SetAllParameters(), 两方法的参数都是结构体(各不相同).

    {给 IDirectSoundBuffer8 关联特效; 不能在缓冲区锁定或播放时使用:}
    function SetFX(
      dwEffectsCount: DWORD;    //特效数目, 即第二个参数 pDSFXDesc 的大小
      pDSFXDesc: PDSEffectDesc; //TDSEffectDesc 结构的数组
      pdwResultCodes: PDWORD    //接收设置结果, 不需要则给 nil
    ): HResult; stdcall;
    
    //可以使用 SefFX(0, nil, nil) 删除缓冲区的所有特效.
    
    {SetFX() 方法用到的结构体:}
    TDSEffectDesc = packed record
      dwSize        : DWORD;     //结构大小
      dwFlags       : DWORD;     //处理标志; 一般给 0
      guidDSFXClass : TGUID;     //指定要使用的效果
      dwReserved1   : DWORD_PTR; //未使用
      dwReserved2   : DWORD_PTR; //未使用
    end;
    
    //DSEffectDesc.dwFlags
    DSFX_LOCHARDWARE = $00000001; //使用硬件处理效果; 这其实在 Direct9.0 还不支持
    DSFX_LOCSOFTWARE = $00000002; //使用软件处理效果; 同默认
    0                             //默认
    
    //DSEffectDesc.guidDSFXClass, 这分别是上面九个接口对应的 GUID 查询标识:
    GUID_DSFX_STANDARD_CHORUS 
    GUID_DSFX_STANDARD_COMPRESSOR 
    GUID_DSFX_STANDARD_DISTORTION 
    GUID_DSFX_STANDARD_ECHO 
    GUID_DSFX_STANDARD_FLANGER 
    GUID_DSFX_STANDARD_GARGLE 
    GUID_DSFX_STANDARD_I3DL2REVERB 
    GUID_DSFX_STANDARD_PARAMEQ 
    GUID_DSFX_WAVES_REVERB 
    
    
    {从 IDirectSoundBuffer8 获取特效对象的方法}
    function GetObjectInPath(
      const rguidObject: TGUID;    //对象查询标识, GUID_DSFX_ ...
      dwIndex: DWORD;              //该特效在 SetFX() 安排特效数组时的索引 
      const rguidInterface: TGUID; //对象的唯一标识, IID_IDirectSoundFX ... 或 IDirectSoundFX ...
      out ppObject                 //返回要获取的特效接口
    ): HResult; stdcall;
    
    {IDirectSoundFXGargle8.SetAllParameters() 需要结构体}
    TDSFXGargle = packed record
      dwRateHz: DWORD;    //频率; 取值范围 1..1000, 默认 20
      dwWaveShape: DWORD; //波形; 三角波(0)、方波(1)
    end;
    
    //TDSFXGargle.dwRateHz 取值范围, 默认是 20
    DSFXGARGLE_RATEHZ_MIN    = 1;    //
    DSFXGARGLE_RATEHZ_MAX    = 1000; //
    
    DSFXGARGLE_WAVE_TRIANGLE = 0; //三角波
    DSFXGARGLE_WAVE_SQUARE   = 1; //方波
    


    为让代码更简洁, 又把前面自定义的 ReadWave 该为了 ReadWave2 (增加了一个 OpenDialog 方法):
    unit ReadWave2;
    
    interface
    
    uses Windows, Classes, SysUtils, MMSystem, Dialogs;
    
    type
    TReadWave = class
    private
      FFileHandle: HMMIO;
      FFormat: TWaveFormatEx;
      FSize: DWORD;
      function GetFormatAndSize(hFile: HMMIO): Boolean;
    public
      destructor Destroy; override;
      function Open(FileName: string): Boolean;            //从文件打开
      function OpenResource(ResName: string): Boolean;     //从资源打开, 资源的指定格式必须是 WAVE
      function OpenDialog: Boolean;                        //从对话框打开
      function Read(pDest: Pointer; Size: DWORD): Boolean; //读出波形数据
      property Format: TWaveFormatEx read FFormat;         //读出格式数据
      property Size: DWORD read FSize;                     //读出波形数据的大小
    end;
    
    implementation
    
    { TReadWave }
    
    destructor TReadWave.Destroy;
    begin
      if FFileHandle > 0 then mmioClose(FFileHandle, 0);
      inherited;
    end;
    
    function TReadWave.GetFormatAndSize(hFile: HMMIO): Boolean;
    var
      ckiRIFF,ckiFmt,ckiData: TMMCKInfo;
    begin
      Result := False;
      if hFile = 0 then Exit;
      ZeroMemory(@ckiRIFF, SizeOf(TMMCKInfo));
      mmioDescend(hFile, @ckiRIFF, nil, MMIO_FINDRIFF);
      if (ckiRIFF.ckid <> FOURCC_RIFF) or (ckiRIFF.fccType <> mmioStringToFOURCC('WAVE',0)) then Exit;
    
      ZeroMemory(@FFormat, SizeOf(TWaveFormatEx));
      ZeroMemory(@ckiFmt, SizeOf(TMMCKInfo));
      ckiFmt.ckid := mmioStringToFOURCC('fmt', 0);
    
      ZeroMemory(@ckiData, SizeOf(TMMCKInfo));
      ckiData.ckid := mmioStringToFOURCC('data', 0);
    
      if (mmioDescend(hFile, @ckiFmt, @ckiRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR) then mmioRead(hFile, @FFormat, SizeOf(TWaveFormatEx));
      mmioAscend(hFile, @ckiFmt, 0);
      if (mmioDescend(hFile, @ckiData, @ckiRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR) then FSize := ckiData.cksize;
    
      Result := FFormat.wFormatTag = WAVE_FORMAT_PCM;
    end;
    
    function TReadWave.Open(FileName: string): Boolean;
    begin
      Result := False;
      if not FileExists(FileName) then Exit;
      if FFileHandle > 0 then mmioClose(FFileHandle, 0);
      FFileHandle := mmioOpen(PChar(FileName), nil, MMIO_READ);
      Result := GetFormatAndSize(FFileHandle);
    end;
    
    function TReadWave.OpenDialog: Boolean;
    begin
      with TOpenDialog.Create(nil) do begin
        Filter := 'Wave File(*.wav)|*.wav';
        if Execute then Result := Open(FileName);
        Free;
      end;
    end;
    
    function TReadWave.OpenResource(ResName: string): Boolean;
    var
      res: TResourceStream;
      mmioInfo: TMMIOInfo;
    begin
      Result := False;
      res := TResourceStream.Create(HInstance, ResName, 'WAVE');
      ZeroMemory(@mmioInfo, SizeOf(TMMIOInfo));
      mmioInfo.fccIOProc := FOURCC_MEM;
      mmioInfo.cchBuffer := res.Size;
      mmioInfo.pchBuffer := res.Memory;
      if FFileHandle > 0 then mmioClose(FFileHandle, 0);
      FFileHandle := mmioOpen(nil, @mmioInfo, MMIO_ALLOCBUF or MMIO_READ);
      Result := GetFormatAndSize(FFileHandle);
      res.Free;
    end;
    
    function TReadWave.Read(pDest: Pointer; Size: DWORD): Boolean;
    begin
      Result := mmioRead(FFileHandle, pDest, Size) = Size;
    end;
    
    end.
    


    测试代码:
    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls, ComCtrls;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;         //打开并播放
        Button2: TButton;         //停止
        TrackBar1: TTrackBar;     //用于调整 TDSFXGargle.dwRateHz
        RadioGroup1: TRadioGroup; //用于调整 TDSFXGargle.dwWaveShape
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure TrackBar1Change(Sender: TObject); //RadioGroup1.OnClick 也关联它
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    uses DirectSound, ReadWave2; //ReadWave2 是刚刚修改过的自定义单元
    
    var
      myDSound: IDirectSound8;
      buf8: IDirectSoundBuffer8;
      fxGargle: IDirectSoundFXGargle8; //IDirectSoundFXGargle8 效果器
    
    {建立设备对象并初始化界面}
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      DirectSoundCreate8(nil, myDSound, nil);
      myDSound.SetCooperativeLevel(Handle, DSSCL_NORMAL);
    
      TrackBar1.Min := DSFXGARGLE_RATEHZ_MIN; //1
      TrackBar1.Max := DSFXGARGLE_RATEHZ_MAX; //1000
      TrackBar1.Position := 20;               //默认值
      TrackBar1.ShowSelRange := False;
      TrackBar1.TickStyle := tsNone;
    
      RadioGroup1.Items.CommaText := 'TRIANGLE, SQUARE';
      RadioGroup1.Columns := 2;
      RadioGroup1.ItemIndex := 0;
      RadioGroup1.OnClick := TrackBar1.OnChange; //两个事件的代码相同
    
      System.ReportMemoryLeaksOnShutdown := true;
    end;
    
    {建立缓冲区、关联特效、获取特效对象并播放}
    procedure TForm1.Button1Click(Sender: TObject);
    var
      buf: IDirectSoundBuffer; //最终需要的是 IDirectSoundBuffer8, 这里的 IDirectSoundBuffer 只是做个桥
      bufDesc: TDSBufferDesc;
      rEffect: TDSEffectDesc;  //SetFX() 方法需要的结构
      wav: TReadWave;
      p1: Pointer;
      n1: DWORD;
    begin
      {经过对自定义单元的修改, 现在调入一个 Wave 很方便}
      wav := TReadWave.Create;
      if not wav.OpenDialog then begin wav.Free; Exit; end;
    
      {获取 IDirectSoundBuffer8 接口对象}
      ZeroMemory(@bufDesc, SizeOf(TDSBufferDesc));
      bufDesc.dwSize := SizeOf(TDSBufferDesc);
      bufDesc.dwFlags := DSBCAPS_CTRLFX; //!
      bufDesc.dwBufferBytes := wav.Size;
      bufDesc.lpwfxFormat := @wav.Format;
      myDSound.CreateSoundBuffer(bufDesc, buf, nil);
      buf.QueryInterface(IID_IDirectSoundBuffer8, buf8);
    
      {载入波形}
      buf8.Lock(0, 0, @p1, @n1, nil, nil, DSBLOCK_ENTIREBUFFER);
      wav.Read(p1, n1);
      wav.Free;
      buf8.Unlock(p1, n1, nil, 0);
    
      {准备 SetFX() 需要的结构}
      ZeroMemory(@rEffect, SizeOf(TDSEffectDesc));
      rEffect.dwSize := SizeOf(TDSEffectDesc);
      rEffect.dwFlags := 0;
      rEffect.guidDSFXClass := GUID_DSFX_STANDARD_GARGLE; //指定是 IDirectSoundFXGargle8 效果器
    
      {关联效果器}
      buf8.SetFX(1, @rEffect, nil); //参数应该是个数组, 既然只有一个元素, 就先省了
    
      {获取效果器对象}
      buf8.GetObjectInPath(GUID_DSFX_STANDARD_GARGLE, 0, IID_IDirectSoundFXGargle8, fxGargle);
    
      {播放}
      buf8.Play(0, 0, DSBPLAY_LOOPING);
    
      //  buf := nil; //局部接口会被自动释放
    end;
    
    {停止播放}
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      if Assigned(buf8) then buf8.Stop;
    end;
    
    {特效变换}
    procedure TForm1.TrackBar1Change(Sender: TObject);
    var
      rGargle: TDSFXGargle;
    begin
      if buf8 = nil then Exit;
      rGargle.dwRateHz := TrackBar1.Position;
      rGargle.dwWaveShape := RadioGroup1.ItemIndex;
      fxGargle.SetAllParameters(rGargle);
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      buf8 := nil;
      myDSound := nil;
    end;
    
    end.
    


    运行效果图:


  • 相关阅读:
    C#内建接口:IComparable
    C#内建接口:IEnumerable
    WPF中使用资源
    WPF中的触发器(Trigger)
    一文详解 | 开放搜索兼容Elasticsearch做召回引擎
    阿里云李飞飞:中国数据库的时与势
    如何构建流量无损的在线应用架构 | 专题开篇
    如何构建一个流量无损的在线应用架构 | 专题中篇
    多任务学习模型之ESMM介绍与实现
    云原生时代的运维体系进化
  • 原文地址:https://www.cnblogs.com/del/p/1941193.html
Copyright © 2020-2023  润新知