网页资讯视频图片知道文库贴吧地图采购
进入贴吧全吧搜索

 
 
 
日一二三四五六
       
       
       
       
       
       

签到排名:今日本吧第个签到,

本吧因你更精彩,明天继续来努力!

本吧签到人数:0

一键签到
成为超级会员,使用一键签到
一键签到
本月漏签0次!
0
成为超级会员,赠送8张补签卡
如何使用?
点击日历上漏签日期,即可进行补签。
连续签到:天  累计签到:天
0
超级会员单次开通12个月以上,赠送连续签到卡3张
使用连续签到卡
08月20日漏签0天
c语言吧 关注:799,016贴子:4,359,097
  • 看贴

  • 图片

  • 吧主推荐

  • 视频

  • 游戏

  • 1 2 3 4 下一页 尾页
  • 64回复贴,共4页
  • ,跳到 页  
<<返回c语言吧
>0< 加载中...

【音乐编程】基于DirectSound的音乐播放与FFT频谱可视化:Part1

  • 只看楼主
  • 收藏

  • 回复
  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
谨以此贴祭拜偶像傅立叶大神与面码酱,度度不要吞楼,可恶
第一章:用DirectSound编写一个简单Wav文件播放器

装了那主题后,秀桌面也有劲了
阅读须知:
虽然是本菜菜出品的教程,但本菜逼不保证刚学C/C++一两年的朋友和对音频的结构解码采样等不感冒的朋友能够完全听懂
倘若你对DirectX一无所知,请阅读本菜的【游戏编程】教程部分的粒子系统,至少阅读完安装配置的那一部分
之所以第一章就上代码,目的在于提高兴趣,我知道,尽管说基础原理最重要,但是更多人喜欢马上搞出个东西来耍耍,所以满足各位愿望先上简易源代码实现wav文件的播放,再解释对其的底层操作与原理
阅读本章仅需要编程编程的概念,原理我会解释,阅读part1 《WAV的简单播放与原理》,part2《音频采样原理与使用libmad解码mp3文件》,part3《使用快速傅立叶变换实现频谱可视化》(火的话再说说怎么把男声变女声或者禽兽声怎么样~~)全三章你需要了解DFT与FFT的概念,傅立叶变换本教程不做解释
因为又是临时的兴起教程兼骗经验,所以仅在本人有空的时候逐步更新,更新完成后统一清楼
面码大爱~~~~


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
现在直接上代码:
头文件部分:

头文件大家都懂的,那个win.h mmsys.h我不想再解释,dsound.h与dsound.lib是本章的关键,注意将他们加入
然后又是main

额,插一句,写代码尽量不要花俏,很多时候本菜菜很想吼一句,我知道你比本菜菜NB,我也知道这么做有利于安全性可维护性可移植....不过写教程的时候能不能写的简单通俗点,不要动不动就用那些看上去很NB的类库模板......
好了,上面的不解释,谁都懂,完成后你可以把code by Baiy换成你的名字,高中党大一党就可以在同学面前装装NB了,你还可以疯狂的BS下做音乐播放器只会拖控件的
切入正题
我们定义以下全局变量

其中第一个是DX的声音缓冲,就是将音频数据写入的地方
第二个用于描述第一个LPDIRECTSOUNDBUFFER,包括描述它是否可以改变(比如说变声),是否可调音量,是否是全局缓冲(最小化程序后音乐会不会暂停播放,因为DX本来就是为游戏编程设计的)等等
第三个是描述波形文件(WAV)格式的,包括它采用何种压缩机制等
第四个用于从文件读取数据的缓冲
第五个句柄不解释
第六个是DirectSound接口
变量定义到此结束



2025-08-20 12:35:04
广告
不感兴趣
开通SVIP免广告
  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

现在开始初始化数据,创建一个DirectSound接口调用DirectSoundCreate函数,它需要窗体的句柄与一个LPDIRECTSOUNDBUFFER指针,因为我们是控制台程序,所以我们调用GetConsoleWindow来获得控制台程序的窗体句柄,然后调用DirectSoundCreate函数,倘若成功,就调用SetCooperativeLevel设置设备协调级别,这是DX的一个特色,在这里应该与声卡有关,假如你想更深入了解这个函数,可以查看DirectInput部分,在这里你可以就这么用,假如成功的话,调用LoadWav,这个函数会加载数据到g_lpdbsBuffer我们等会讨论它的实现,加载完成后,调用play将wav音乐播放出来,很简单吧.


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

现在进行LoadFile函数,首先我们定义一个HMMIO,这是一个文件句柄,很像我们常用的FILE
,MMCKINFO用于记录它的类型,PCMWAVFORMAT就相当于文件格式,PCM乃脉冲编码调制,恩,简单的说就是编码格式,PCM格式的缓存区里有一些比较奇葩的数学运算,
,之后?mmioOpen打开这个文件,用fopen的都懂得,打开文件后,寻找RIFF块,稍后解释,你可以直接理解成文件头,如果失败,那么就关闭它


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

之后就是验证这个是不是wave文件了,看代码1,应该不难理解,然后获取wav format,其中包括了音频格式,声道数量,采样率,字节率,采样点位数等等,代码3验证它是否采用PCM编码,实际上绝大多数都是采用这种编码格式,要是觉得自己NB的话,甚至可以自己写一个


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

最后找data块,wav格式在源代码发布完毕后本菜逼统一讲解


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

之后,之后就是读取文件了,第一行new了一个缓冲区,大小是数据块的大小,因为这个程序暂且不用频谱,所以我们将整个文件都加载到缓存区内,加载完成后关闭这个文件句柄,和close一样,之后就是创建sound的缓存区了,我们先要描述这个缓存区,比如大小啊,格式啊,其实大多都是从wav文件读取出来的,注意一下那个flag,我是这样定义的

额,我还是复制粘贴一段吧
DSBCAPS_CTRL3D //缓冲区具有的3D音效控制能力,不能与DSBCAPS_CTRLPAN一起使用
DSBCAPS_CTRLFREQUENCY //可设置采样频率
DSBCAPS_CTRLFX //缓冲区支持特效处理,但缓冲区必须够大,可容纳更多的数据
DSBCAPS_CTRLPAN //缓冲区可以控制声道
DSBCAPS_CTRLPOSITIONNOTIFY //缓冲区具有播放位置通知能力
DSBCAPS_CTRLVOLUME //缓冲区可设置音量大小
DSBCAPS_GLOBALFOCUS //缓冲是一个全局声音资源,当前程序切换到其他程序依然可以继续播放.
DSBCAPS_LOCDEFER //缓冲区可绑定硬件内存或者软件内存来播放声音
DSBCAPS_LOCHARDWARE //缓冲区必须使用硬件的混声器,如果不支持硬件内存或者混声器,都会导致创建缓冲区失败.
DSBCAPS_LOCSOFTWARE //缓冲区使用软件内存或者使用软件混音
DSBCAPS_MUTE3DATMAXDISTANCE //超过声音可听的最大距离,将停止播放声音
DSBCAPS_PRIMARYBUFFER //说明缓冲区的为主缓冲区(如果没说明,则用作次缓冲区)
DSBCAPS_STATIC //自动使用硬件内存做缓冲区
DSBCAPS_STICKYFOCUS //当程序切换到其他不使用DIRECTSOUND 的程序时,缓冲区继续播放声音,但无法如常进行其他处理
完成后CreateBuffer


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

最后锁定缓存区,把数据扔到里面去,完工,哇哦,整个代码真短,应该吧友智商都比本菜逼这种天然呆要高,实在不理解你可以选择复制粘贴


2025-08-20 12:29:04
广告
不感兴趣
开通SVIP免广告
  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
//我是代码
#include <Windows.h>
#include <mmsystem.h>
#include <dsound.h>
#include <stdio.h>
#pragma comment(lib,"Dsound.lib")
#pragma comment(lib,"winmm.lib")
#ifndef DSBCAPS_CTRLDEFAULT
#define DSBCAPS_CTRLDEFAULT (DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME|DSBCAPS_GLOBALFOCUS)
#endif
//Global var//////////////////////////////////////////////////
LPDIRECTSOUNDBUFFER g_lpdbsBuffer = NULL;
DSBUFFERDESC g_dsbd;
WAVEFORMATEX g_wfmx;
char* g_sndBuffer = NULL;
HWND g_hWnd =NULL;
LPDIRECTSOUND g_lpds = NULL;
////////////////////////////////////////////////////////////
BOOL LoadWav(TCHAR *FileName,UINT Flag)
{
HMMIO handle ;
MMCKINFO mmckriff,mmckIn;
PCMWAVEFORMAT pwfm;
memset(&mmckriff,0,sizeof(MMCKINFO));
if((handle= mmioOpen(FileName,NULL,MMIO_READ|MMIO_ALLOCBUF))==NULL)
return FALSE;
if(0 !=mmioDescend(handle,&mmckriff,NULL,0))
{
mmioClose(handle,0);
return FALSE;
}
if(mmckriff.ckid !=FOURCC_RIFF||mmckriff.fccType !=mmioFOURCC('W','A','V','E'))
{
mmioClose(handle,0);
return FAL****ckIn.ckid = mmioFOURCC('f','m','t',' ');
if(0 !=mmioDescend(handle,&mmckIn,&mmckriff,MMIO_FINDCHUNK))
{
mmioClose(handle,0);
return FALSE;
}
if(mmioRead(handle,(HPSTR)&pwfm,sizeof(PCMWAVEFORMAT))!=sizeof(PCMWAVEFORMAT))
{
mmioClose(handle,0);
return FALSE;
} if(pwfm.wf.wFormatTag != WAVE_FORMAT_PCM)
{
mmioClose(handle,0);
return FALSE;
}
memcpy(&g_wfmx,&pwfm,sizeof(pwfm));
g_wfmx.cbSize =0; if(0 != mmioAscend(handle,&mmckIn,0))
{
mmioClose(handle,0);
return FAL****ckIn.ckid = mmioFOURCC('d','a','t','a');
if(0 !=mmioDescend(handle,&mmckIn,&mmckriff,MMIO_FINDCHUNK))
{
mmioClose(handle,0);
return FALSE;
} g_sndBuffer = new char[mmckIn.cksize];
mmioRead(handle,(HPSTR)g_sndBuffer,mmckIn.cksize); mmioClose(handle,0);
g_dsbd.dwSize = sizeof(DSBUFFERDESC);
g_dsbd.dwBufferBytes =mmckIn.cksize;
g_dsbd.dwFlags = DSBCAPS_CTRLDEFAULT;
g_dsbd.lpwfxFormat =&g_wfmx;
if(FAILED(g_lpds ->CreateSoundBuffer(&g_dsbd,&g_lpdbsBuffer,NULL)))
{
delete [] g_sndBuffer;
return FALSE;
}
VOID* pDSLockedBuffer =NULL;
DWORD dwDSLockedBufferSize =0;
if(g_lpdbsBuffer ->Lock(0,mmckIn.cksize,&pDSLockedBuffer,&dwDSLockedBufferSize,NULL,NULL,0L))
return FALSE;
memcpy(pDSLockedBuffer,g_sndBuffer,mmckIn.cksize); if(FAILED(g_lpdbsBuffer ->Unlock(pDSLockedBuffer,dwDSLockedBufferSize,NULL,0)))
{
delete [] g_sndBuffer;
return FALSE;
} return TRUE;
}
int main()
{
printf("-----------------------------------------------\n");
printf("------------------WAVE Player------------------\n");
printf("--------Code by Baiy 2012/9/18-------------\n");
printf("-----------------------------------------------\n");
char WAVFilePath[MAX_PATH];
printf("\n Input the wavFile Path:");
scanf("%s",WAVFilePath);
g_hWnd=GetConsoleWindow();
if(DirectSoundCreate(NULL,&g_lpds,NULL) == DS_OK)
if(g_lpds ->SetCooperativeLevel(g_hWnd,DSSCL_NORMAL)==DS_OK)
{
if(LoadWav(WAVFilePath,DSBCAPS_CTRLDEFAULT))
{
g_lpdbsBuffer->Play(0,0,0);
printf("\nWav Playing......");
}
else
{
return 0;
}
}
getchar();getchar();
return 0;
}



  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
12点了,吃饱午睡后再说,晚上见


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
咳咳,minna,话说还有人么,我们继续


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
part1_2
音频的基础知识(凭本菜逼的记忆,可能不完全准确)
说道音频,不得不说的就是音频是如何取得的,只有知道音频是如何取得并存储在计算机中的,才能明白音频是如何播放的
关于音频的取得,学过高中物理我们都知道,自然界的声音是以波的形式传播的,那么计算机是如何将波保存在硬盘中的呢,这里不得不提到一个概念,采样频率
.
显然,自然界的波形不可能完全用sinx来表示(实际上有很多的音乐直接由数学公式生成后合成,比如笛子的声音由数学公式可以很好的模拟,在此膜拜那些大神),于是,我们就规定每隔一段时间对声波进行采样,记录下声波的振幅,实际上,保存在计算机中的音波就是一个一个的点,在播放的时候再将它还原成波形,显然的,采样的频率越高,能够还原的还原度也就越高,采样频率我们用HZ这个单位来表示(用以纪念赫兹大神),假如说100HZ的采样频率,就是每秒钟对声波进行100次采样,虽然说采样频率自然是越高越好,但是考虑到内存与质量的平衡,某大神说了(名字真的忘了),以 X HZ频率的采样,最大可以对X/2 HZ频率的波形进行采样,假如超过了,采样的后还原的波形就可能不伦不类
我们的声卡一般以11025Hz,22050Hz 44100Hz进行采样,而记录振幅的数据有8位的也有16位的,显然,16位的可以记录65536种音高,自然效果也会更好



  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
tips:
个人十分偏爱音轨音乐格式,就是那种只给出演奏方法曲目让程序合成的那种方法


体积小,效果好,这会让你的游戏编程耳目一新


  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
关于声道
传说中的立体声,听多了音乐的人应该很清楚,有时左耳播发的和右耳耳机播放的不一样,这就是两个声道的结果让人感觉似乎是在现场演奏一样,你可以理解为2声道就是有两个音乐同时播放


2025-08-20 12:23:04
广告
不感兴趣
开通SVIP免广告
  • 汇蓝鸟
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
Part1教程结束,欢迎纠错或者开喷,我更愿意谈谈关于面码酱的东西


登录百度账号

扫二维码下载贴吧客户端

下载贴吧APP
看高清直播、视频!
  • 贴吧页面意见反馈
  • 违规贴吧举报反馈通道
  • 贴吧违规信息处理公示
  • 1 2 3 4 下一页 尾页
  • 64回复贴,共4页
  • ,跳到 页  
<<返回c语言吧
分享到:
©2025 Baidu贴吧协议|隐私政策|吧主制度|意见反馈|网络谣言警示