文档章节

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

涂孟超
 涂孟超
发布于 2014/09/26 15:37
字数 1412
阅读 11
收藏 0
点赞 0
评论 0

只有使用 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.


运行效果图:


本文转载自:http://www.cnblogs.com/del/archive/2011/01/21/1941193.html

共有 人打赏支持
涂孟超
粉丝 12
博文 2004
码字总数 14107
作品 0
深圳
程序员
我是电音之王!FLStudio学习路线图

初步认识水果音乐软件 初步认识水果音乐 终结篇 水果音乐的安装 基本面板的认识与控制 编辑菜单基本操作的认识 通道菜单的认识及基本操作 view浏览器的基本操作 控制面板的基本操作 option菜...

棋帅小七 ⋅ 2017/12/01 ⋅ 0

delphi 与 php 相关连的知识点总结.

为了维护xampps这项目, delphi还必须得会那么一点. 总不能碰到问题就问人吧, 感觉太不长进了. 经过几天的努力编写与学习, 以站在php角度来思考delphi, 感觉php强大的确是真理. 但delphi的理念...

Tuesday ⋅ 2013/12/12 ⋅ 1

Genesis-3D新手入门——10.粒子系统

粒子系统 粒子系统是Genesis-3D编辑器里一个重要的功能,在一个游戏里,优秀的粒子使用手法可以极大地增加游戏的视觉效果,直接为你设计的游戏加分。因此,要想让你的游戏变得华丽,有接近顶...

lengche ⋅ 2014/02/20 ⋅ 0

“梦”游戏引擎--Yume Engine

概要 Yume Engine 探索新的渲染器设计思想,尽量实现尽可能简单。受到其他开源渲染器的启发,打算支持 D3D11,D3D12 和 OpenGL(未来的 Vulkan),这个引擎努力尽可能地依赖于平台。 在初步规...

匿名 ⋅ 2016/12/24 ⋅ 0

分形火焰编辑器--Apophysis 7x

Apophysis是一个开放源代码的分形火焰编辑器也是一个为微软windows软件制作的渲染器。它是由Mark Townsend将原有的scott draves的原始c代码转化为delphi代码并且添加了一个图形用户界面而创造...

文洁洁洁 ⋅ 2015/02/11 ⋅ 0

几句代码快速集成自定义转场效果+ 全手势驱动

原文出处:wazrx 写在前面 在简书写完第一篇的自定义转场文章后,已经很久没有碰过转场了,毕竟在公司,功能实现才是最重要的,这些转场的动效,只能是点睛之笔,不太容易被重视,不过我的第...

秦无炎 ⋅ 2016/12/05 ⋅ 0

Android自定义控件三部曲文章索引

前言:在我从C++转到Android时,就被Android里炫彩斑斓的自定义控件深深折服,想知道如果想利用C++实现这些功能,那是相当困难的。从那时候起,我就想,等我学会了自定义控件,一定要写一篇系...

harvic880925 ⋅ 2016/11/25 ⋅ 0

Android 动画:你真的会使用插值器与估值器吗?(含详细实例教学)

前言 动画的使用 是 开发中常用的知识 可是动画的种类繁多、使用复杂,每当需要 采用自定义动画 实现 复杂的动画效果时,很多开发者就显得束手无策 Android中 补间动画 & 属性动画实现动画的...

Carson_Ho ⋅ 2017/06/27 ⋅ 0

LibreOffice for Android 开发进展

拜于 Google Summer of Code,现在 LibreOffice for Android 已经有了原型。 Michael Meeks 在博客上分享了他指导的学生 Iain Billet 在 LibreOffice for Android 上的进展,目前 LibreOffic...

oschina ⋅ 2012/07/16 ⋅ 16

.NET源码混淆工具对比及下载

.NET开发人员都知道,.NET的优点是比较多的,如:标准集成,简化应用,对移动设备的支持等,它是一个巨大的跨时代进步。但.NET程序有个致命的缺点:易被反编译。试想一个团队花了几个月甚至几...

nautygirl ⋅ 2013/04/17 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring | IOC AOP 注解 简单使用

写在前面的话 很久没更新笔记了,有人会抱怨:小冯啊,你是不是在偷懒啊,没有学习了。老哥,真的冤枉:我觉得我自己很菜,还在努力学习呢,正在学习Vue.js做管理系统呢。即便这样,我还是不...

Wenyi_Feng ⋅ 今天 ⋅ 0

博客迁移到 https://www.jianshu.com/u/aa501451a235

博客迁移到 https://www.jianshu.com/u/aa501451a235 本博客不再更新

为为02 ⋅ 今天 ⋅ 0

win10怎么彻底关闭自动更新

win10自带的更新每天都很多,每一次下载都要占用大量网络,而且安装要等得时间也蛮久的。 工具/原料 Win10 方法/步骤 单击左下角开始菜单点击设置图标进入设置界面 在设置窗口中输入“服务”...

阿K1225 ⋅ 今天 ⋅ 0

Elasticsearch 6.3.0 SQL功能使用案例分享

The best elasticsearch highlevel java rest api-----bboss Elasticsearch 6.3.0 官方新推出的SQL检索插件非常不错,本文一个实际案例来介绍其使用方法。 1.代码中的sql检索 @Testpu...

bboss ⋅ 今天 ⋅ 0

informix数据库在linux中的安装以及用java/c/c++访问

一、安装前准备 安装JDK(略) 到IBM官网上下载informix软件:iif.12.10.FC9DE.linux-x86_64.tar放在某个大家都可以访问的目录比如:/mypkg,并解压到该目录下。 我也放到了百度云和天翼云上...

wangxuwei ⋅ 今天 ⋅ 0

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 今天 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 今天 ⋅ 0

浅谈设计模式之工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻...

佛系程序猿灬 ⋅ 今天 ⋅ 0

Dockerfile基础命令总结

FROM 指定使用的基础base image FROM scratch # 制作base image ,不使用任何基础imageFROM centos # 使用base imageFROM ubuntu:14.04 尽量使用官方的base image,为了安全 LABEL 描述作...

ExtreU ⋅ 昨天 ⋅ 0

存储,对比私有云和公有云的不同

导读 说起公共存储,很难不与后网络公司时代的选择性外包联系起来,但尽管如此,它还是具备着简单和固有的可用性。公共存储的名字听起来也缺乏专有性,很像是把东西直接堆放在那里而不会得到...

问题终结者 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部