失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > nes 红白机模拟器 第6篇 声音支持

nes 红白机模拟器 第6篇 声音支持

时间:2023-01-19 06:55:44

相关推荐

nes 红白机模拟器 第6篇 声音支持

InfoNES 源码中并没有包含 linux 的声音支持。

但提供 wince 和 win 的工程,文件,通过分析,win 的 DirectSound 发声,在使用 linux ALSA 实现。

先使用 DirectSound 模仿写一个 播放 wav 的程序。

为了简单,我这里使用 vc++ 6.0 (vs 实在太大了,电脑装上太卡)。

新建一个 mfc exe 项目,基于对话框。放一个按钮,双击添加事件。

添加头文件引用

#include <mmsystem.h>

#pragma comment(lib,"Winmm.lib")

点击 开始播放 事件

void CWavDlg::OnButtonPlay()

{

// TODO: Add your control notification handler code here

PlaySound(_T("1.wav"), NULL, SND_NOWAIT);

}

在 debug 目录,放一个 1.wav 生成可执行文件,点 开始播放, 果然可以播放出来。(win 的东西就是这么简单实用)。

分析 InfoNES_Sound_Win.cpp

类初始化

1 DIRSOUND::DIRSOUND(HWND hwnd) 2 { 3DWORD ret; 4WORD x; 5 6// init variables 7iCnt = Loops * 3 / 4; // loops:20 iCnt:20*3/4 = 15 8 9for ( x = 0;x < ds_NUMCHANNELS; x++ ) // ds_NUMCHANNELS = 810{11 lpdsb[x] = NULL; // DirectSoundBuffer lpdsb[ds_NUMCHANNELS]; 8个 初始化为 NULL12}13 14// init DirectSound 创建一个 DirectSound 里面有 8个 DirectSoundBuffer15ret = DirectSoundCreate(NULL, &lpdirsnd, NULL);16 17if (ret != DS_OK)18{19 InfoNES_MessageBox( "Sound Card is needed to execute this application." );20 exit(-1);21}22 23 // set cooperative level24 #if 125//设置属性不重要26ret = lpdirsnd->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);27 #else28ret = lpdirsnd->SetCooperativeLevel( hwnd, DSSCL_NORMAL );29 #endif30 31if ( ret != DS_OK )32{33InfoNES_MessageBox( "SetCooperativeLevel() Failed." );34 exit(-1);35}36 }

SoundOpen

1 WORD DIRSOUND::AllocChannel(void) 2 { 3WORD x; 45//判断 lpdsb 找到一个 为空的 这里应该返回0 6for (x=0;x<ds_NUMCHANNELS;x++) 7{ 8 if (lpdsb[x] == NULL) 9 {10 break;11 }12}13 14if ( x == ds_NUMCHANNELS )15{16/* No available channel */17InfoNES_MessageBox( "AllocChannel() Failed." );18 exit(-1); 19}20 21return (x);22 }23 24 void DIRSOUND::CreateBuffer(WORD channel)25 {26DSBUFFERDESC dsbdesc; //SoundBuffer 描述27PCMWAVEFORMAT pcmwf; //wav fmt 格式描述28HRESULT hr;29 30//清031memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));32//pcm 格式33pcmwf.wf.wFormatTag= WAVE_FORMAT_PCM;34//1个声道35pcmwf.wf.nChannels = ds_CHANSPERSAMPLE;36//采样率 4410037pcmwf.wf.nSamplesPerSec = ds_SAMPLERATE;38//对齐 采样率 / 8 * 声道数 = 44100 / 8 * 1 = 5512.539pcmwf.wf.nBlockAlign = ds_CHANSPERSAMPLE * ds_BITSPERSAMPLE / 8;40//缓存区大小 44100*5512.5 = 24310125041pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;42//8位 声音43pcmwf.wBitsPerSample = ds_BITSPERSAMPLE;44 45//清046memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));47dsbdesc.dwSize= sizeof(DSBUFFERDESC);48dsbdesc.dwFlags = 0;49//缓存大小 735 * 15 = 1102550dsbdesc.dwBufferBytes = len[channel]*Loops;51dsbdesc.lpwfxFormat= (LPWAVEFORMATEX)&pcmwf;52 53hr = lpdirsnd->CreateSoundBuffer(&dsbdesc, &lpdsb[channel], NULL);54 55if (hr != DS_OK)56{57 InfoNES_MessageBox( "CreateSoundBuffer() Failed." );58 exit(-1);59}60 }61 62 //samples_per_sync = 735 sample_rate = 4410063 BOOL DIRSOUND::SoundOpen(int samples_per_sync, int sample_rate)64 {65//ch 1 WORD unsigned short 类型 , 创建一个 通道 , 返回 第0 个 SoundBuffer66ch1 = AllocChannel();6768/** 69* 参数定义70* BYTE*sound[ds_NUMCHANNELS];71* DWORD len[ds_NUMCHANNELS];72*/73//申请了一个 735 大小的 Byte74sound[ch1] = new BYTE[ samples_per_sync ];75//记录了 大小 73576len[ch1]= samples_per_sync;77 78if ( sound[ch1] == NULL )79{80 InfoNES_MessageBox( "new BYTE[] Failed." );81 exit(-1);82}8384//创建缓存区85CreateBuffer( ch1 );86 87/* Clear buffer */88FillMemory( sound[ch1], len[ch1], 0 ); 89//执行15次90for ( int i = 0; i < Loops; i++ )91 SoundOutput( len[ch1], sound[ch1] ); 92 93/* Begin to play sound */94Start( ch1, TRUE );95 96 return TRUE;97 }

SoundOutput

1 //初始化时 执行 samples:735 wave:NULL 2 BOOL DIRSOUND::SoundOutput(int samples, BYTE *wave) 3 { 4/* Buffering sound data */ 5//将 wave 复制到 sound 6CopyMemory( sound[ ch1 ], wave, samples ); 7 8/* Copying to sound data buffer */ 9FillBuffer( ch1 ); 10 11/* Play if Counter reaches buffer edge */12//初始化时 iCnt:15 Loops:if ( Loops == ++iCnt )14{15 iCnt = 0;16}17//这里 iCnt = 1618return TRUE;19 }20 void DIRSOUND::FillBuffer( WORD channel )21 {22LPVOID write1;23DWORD length1;24LPVOID write2;25DWORD length2;26HRESULT hr;27 28//得到要写入的地址29hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, 0 );30 31//如果返回DSERR_BUFFERLOST,释放并重试锁定32if (hr == DSERR_BUFFERLOST)33{34 lpdsb[channel]->Restore();35 36 hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, 0 );37}38 39if (hr != DS_OK)40{41 InfoNES_MessageBox( "Lock() Failed." );42 exit(-1);43}44 45//写入数据46CopyMemory( write1, sound[channel], length1 );47 48if (write2 != NULL)49{50 CopyMemory(write2, sound[channel] + length1, length2);51}52//解锁53hr = lpdsb[channel]->Unlock(write1, length1, write2, length2);54 55if (hr != DS_OK)56{57 InfoNES_MessageBox( "Unlock() Failed." );58 exit(-1);59}60 }

Play

1 //初始化时 ch1 重复播放 2 void DIRSOUND::Start(WORD channel, BOOL looping) 3 { 4HRESULT hr; 5 6hr = lpdsb[channel]->Play( 0, 0, looping == TRUE ? DSBPLAY_LOOPING : 0 ); 7 8if ( hr != DS_OK ) 9{10 InfoNES_MessageBox( "Play() Failed." );11 exit(-1);12}13 }

播放调用

1 void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 ) 2 { 3//rec_freq = 735 4BYTE wave[ rec_freq ]; 5//取了 wave1~5 的平均值 6for ( int i = 0; i < rec_freq; i++) 7{ 8 wave[i] = ( wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i] ) / 5; 9}10 #if 111if (!lpSndDevice->SoundOutput( samples, wave ) )12 #else13if (!lpSndDevice->SoundOutput( samples, wave3 ) )14 #endif15{16 InfoNES_MessageBox( "SoundOutput() Failed." );17 exit(0);18}19 }

最后总结得到几个有用的参数:

声道数 1

采样率 44100

采样位数 8

每次播放块大小(NES APU 每次生成一块)735

更新 -11-04

已移值到 alsa-lib 支持,播放正常,已更新至 github 。 可以在 置顶博文中找地址。

如果觉得《nes 红白机模拟器 第6篇 声音支持》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。