Lib
QOLを高める
sound.cpp
Go to the documentation of this file.
1 #include "stdafx.h"
2 #include "include/sound.h"
3 #include "include/debug.h"
4 #include "include/exceptions.h"
5 #include <algorithm>
6 
7 #include <mmsystem.h>
8 #pragma comment(lib, "winmm.lib")
9 
10 namespace yappy {
11 namespace sound {
12 
13 using error::MmioError;
14 using error::OggVorbisError;
16 using error::XAudioError;
17 
18 namespace {
19 
20 void loadWaveFile(SoundEffect *out, const wchar_t *path)
21 {
22  file::Bytes bin = file::loadFile(path);
23 
24  MMIOINFO mmioInfo = { 0 };
25  mmioInfo.pchBuffer = reinterpret_cast<HPSTR>(bin.data());
26  mmioInfo.fccIOProc = FOURCC_MEM;
27  mmioInfo.cchBuffer = static_cast<LONG>(bin.size());
28 
29  HMMIO tmpHMmio = mmioOpen(0, &mmioInfo, MMIO_READ);
30  if (tmpHMmio == nullptr) {
31  throw MmioError("mmioOpen() failed", mmioInfo.wErrorRet);
32  }
33  std::unique_ptr<HMMIO, hmmioDeleter> hMmio(tmpHMmio);
34 
35  // enter "WAVE"
36  MMRESULT mmRes = MMSYSERR_NOERROR;
37  MMCKINFO riffChunk = { 0 };
38  riffChunk.fccType = mmioFOURCC('W', 'A', 'V', 'E');
39  mmRes = mmioDescend(hMmio.get(), &riffChunk, nullptr, MMIO_FINDRIFF);
40  if (mmRes != MMSYSERR_NOERROR) {
41  throw MmioError("mmioDescend() failed", mmRes);
42  }
43  // enter "fmt "
44  MMCKINFO formatChunk = { 0 };
45  formatChunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
46  mmRes = mmioDescend(hMmio.get(), &formatChunk, &riffChunk, MMIO_FINDCHUNK);
47  if (mmRes != MMSYSERR_NOERROR) {
48  throw MmioError("mmioDescend() failed", mmRes);
49  }
50  // read "fmt "
51  ::ZeroMemory(&out->format, sizeof(out->format));
52  DWORD fmtSize = std::min(formatChunk.cksize, static_cast<DWORD>(sizeof(out->format)));
53  DWORD size = mmioRead(hMmio.get(), reinterpret_cast<HPSTR>(&out->format), fmtSize);
54  if (size != fmtSize) {
55  throw MmioError("mmioRead() failed", size);
56  }
57  // leave "fmt "
58  mmRes = mmioAscend(hMmio.get(), &formatChunk, 0);
59  if (mmRes != MMSYSERR_NOERROR) {
60  throw MmioError("mmioAscend() failed", mmRes);
61  }
62  // enter "data"
63  MMCKINFO dataChunk = { 0 };
64  dataChunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
65  mmRes = mmioDescend(hMmio.get(), &dataChunk, &riffChunk, MMIO_FINDCHUNK);
66  if (mmRes != MMSYSERR_NOERROR) {
67  throw MmioError("mmioDescend() failed", mmRes);
68  }
69  // read "data"
70  if (dataChunk.cksize > SoundFileSizeMax) {
71  throw MmioError("data size too large", 0);
72  }
73  out->samples.resize(dataChunk.cksize);
74  size = mmioRead(hMmio.get(), reinterpret_cast<HPSTR>(out->samples.data()), dataChunk.cksize);
75  if (size != dataChunk.cksize) {
76  throw MmioError("mmioRead() failed", size);
77  }
78 }
79 
80 } // namespace
81 
83  m_pBgmBuffer(new char[BgmBufferSize * BgmBufferCount]),
84  m_writePos(0)
85 {
86  debug::writeLine(L"Initializing XAudio2...");
87 
88  HRESULT hr = S_OK;
89 
90  IXAudio2 *ptmpIXAudio2 = nullptr;
91  UINT32 flags = 0;
92 #ifdef _DEBUG
93  flags = XAUDIO2_DEBUG_ENGINE;
94 #endif
95  hr = ::XAudio2Create(&ptmpIXAudio2, flags);
96  checkDXResult<XAudioError>(hr, "XAudio2Create() failed");
97  m_pIXAudio.reset(ptmpIXAudio2);
98 
99  IXAudio2MasteringVoice *ptmpMasterVoice = nullptr;
100  hr = m_pIXAudio->CreateMasteringVoice(&ptmpMasterVoice);
101  checkDXResult<XAudioError>(hr, "IXAudio2::CreateMasteringVoice() failed");
102  m_pMasterVoice.reset(ptmpMasterVoice);
103 
104  debug::writeLine(L"Initializing XAudio2 OK");
105 }
106 
108  debug::writeLine(L"Finalize XAudio2");
109 }
110 
112 {
113  processFrameSe();
114  processFrameBgm();
115 }
116 
118 {
119  auto res = std::make_shared<SoundEffect>();
120  loadWaveFile(res.get(), path);
121  return res;
122 }
123 
125 {
126  // find playing src voice list entry
127  PyaingSeElem *ppEntry = findFreeSeEntry();
128  if (ppEntry == nullptr) {
129  debug::writeLine("Warning: SE playing list is full!");
130  return;
131  }
132  SeResourcePtr &entryBuf = std::get<0>(*ppEntry);
133  SourceVoicePtr &entryVoice = std::get<1>(*ppEntry);
134  ASSERT(entryBuf == nullptr);
135  ASSERT(entryVoice == nullptr);
136 
137  XAUDIO2_BUFFER buffer = { 0 };
138  ASSERT(se->samples.size() <= file::FileSizeMax);
139  buffer.AudioBytes = static_cast<UINT32>(se->samples.size());
140  buffer.pAudioData = se->samples.data();
141  buffer.Flags = XAUDIO2_END_OF_STREAM;
142 
143  // create source voice
144  HRESULT hr = S_OK;
145  IXAudio2SourceVoice *ptmpSrcVoice = nullptr;
146  hr = m_pIXAudio->CreateSourceVoice(&ptmpSrcVoice, &se->format);
147  checkDXResult<XAudioError>(hr, "IXAudio2::CreateSourceVoice() failed");
148  SourceVoicePtr pSrcVoice(ptmpSrcVoice);
149 
150  // submit source buffer
151  hr = pSrcVoice->SubmitSourceBuffer(&buffer);
152  checkDXResult<XAudioError>(hr, "IXAudio2SourceVoice::SubmitSourceBuffer() failed");
153  hr = pSrcVoice->Start();
154  checkDXResult<XAudioError>(hr, "IXAudio2SourceVoice::Start() failed");
155 
156  // keep shared_ptr reference in *this
157  entryBuf = se;
158  // set the source voice element
159  entryVoice = std::move(pSrcVoice);
160 }
161 
163 {
164  for (const auto &entry : m_playingSeList) {
165  const SeResourcePtr &entryBuf = std::get<0>(entry);
166  const SourceVoicePtr &entryVoice = std::get<1>(entry);
167  if (entryBuf == nullptr) {
168  ASSERT(entryVoice == nullptr);
169  continue;
170  }
171  // check if playing is end
172  XAUDIO2_VOICE_STATE state = { 0 };
173  entryVoice->GetState(&state);
174  if (state.BuffersQueued != 0) {
175  return false;
176  }
177  }
178  return true;
179 }
180 
182 {
183  // release SrcVoice, then decrement refcnt of wave buf
184  for (auto &entry : m_playingSeList) {
185  SeResourcePtr &entryBuf = std::get<0>(entry);
186  SourceVoicePtr &entryVoice = std::get<1>(entry);
187  entryVoice.reset();
188  entryBuf.reset();
189  }
190 }
191 
192 XAudio2::PyaingSeElem *XAudio2::findFreeSeEntry()
193 {
194  for (auto &entry : m_playingSeList) {
195  SeResourcePtr &entryBuf = std::get<0>(entry);
196  SourceVoicePtr &entryVoice = std::get<1>(entry);
197  if (entryBuf == nullptr) {
198  ASSERT(entryVoice == nullptr);
199  return &entry;
200  }
201  }
202  return nullptr;
203 }
204 
205 void XAudio2::processFrameSe()
206 {
207  // poll playing state and release if playing completed
208  for (auto &entry : m_playingSeList) {
209  SeResourcePtr &entryBuf = std::get<0>(entry);
210  SourceVoicePtr &entryVoice = std::get<1>(entry);
211 
212  if (entryBuf == nullptr) {
213  ASSERT(entryVoice == nullptr);
214  continue;
215  }
216  // get playing state
217  XAUDIO2_VOICE_STATE state = { 0 };
218  entryVoice->GetState(&state);
219  if (state.BuffersQueued == 0) {
220  // DestroyVoice(), stop playing raw buf, set nullptr
221  entryVoice.reset();
222  // Release reference to raw wave buffer
223  entryBuf.reset();
224  }
225  }
226 }
227 
228 
230 {
231  auto res = std::make_shared<Bgm>(file::loadFile(path));
232  return res;
233 }
234 
236 {
237  int ret = 0;
238  HRESULT hr = S_OK;
239 
240  stopBgm();
241 
242  // ovinfo
243  vorbis_info *info = ov_info(bgm->ovFp(), -1);
244  if (info == nullptr) {
245  throw OggVorbisError("ov_info() failed", 0);
246  }
247  debug::writef(L"ov_info: channels=%d, rate=%ld", info->channels, info->rate);
248  // WAVEFORMAT from ovinfo
249  WAVEFORMATEX format = { 0 };
250  format.wFormatTag = WAVE_FORMAT_PCM;
251  format.nChannels = info->channels;
252  format.nSamplesPerSec = info->rate;
253  format.wBitsPerSample = 16;
254  format.nBlockAlign = info->channels * format.wBitsPerSample / 8;
255  format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
256  format.cbSize = 0;
257 
258  // create source voice using WAVEFORMAT
259  IXAudio2SourceVoice *ptmpSrcVoice = nullptr;
260  hr = m_pIXAudio->CreateSourceVoice(&ptmpSrcVoice, &format);
261  checkDXResult<XAudioError>(hr, "IXAudio2::CreateSourceVoice() failed");
262  m_pBgmVoice.reset(ptmpSrcVoice);
263 
264  // keep reference to ogg bin
265  m_playingBgm = bgm;
266 
267  hr = m_pBgmVoice->Start();
268  checkDXResult<XAudioError>(hr, "IXAudio2SourceVoice::Start() failed");
269 
270  debug::writeLine(L"playBgm OK");
271 }
272 
274 {
275  // DestroyVoice(), stop playing, set nullptr
276  m_pBgmVoice.reset();
277  // release reference to ogg bin
278  m_playingBgm.reset();
279 
280  debug::writeLine(L"stopBgm OK");
281 }
282 
283 void XAudio2::processFrameBgm()
284 {
285  HRESULT hr = S_OK;
286 
287  if (m_pBgmVoice == nullptr) {
288  // not playing
289  return;
290  }
291  ASSERT(m_playingBgm != nullptr);
292 
293  XAUDIO2_VOICE_STATE state = { 0 };
294  m_pBgmVoice->GetState(&state);
295  if (state.BuffersQueued >= BgmBufferCount) {
296  // queue is full, do nothing
297  return;
298  }
299 
300  // fill m_pBgmBuffer[base:base+BgmBufferSize-1]
301  OggVorbis_File *fp = m_playingBgm->ovFp();
302  uint32_t readSum = 0;
303  uint32_t base = m_writePos * BgmBufferSize;
304  while (BgmBufferSize - readSum >= BgmOvReadSize) {
305  long size = ::ov_read(fp,
306  &m_pBgmBuffer[base + readSum],
307  BgmOvReadSize, 0, 2, 1, nullptr);
308  if (size < 0) {
309  throw OggVorbisError("ov_read() failed", size);
310  }
311  readSum += size;
312  if (size == 0) {
313  // stream end; seek to loop point
314  // TODO: loop point
315  int ret = ::ov_time_seek(fp, 0.0);
316  if (ret < 0) {
317  throw OggVorbisError("ov_time_seek() failed", ret);
318  }
319  }
320  }
321 
322  // submit source buffer (add to queue)
323  XAUDIO2_BUFFER buffer = { 0 };
324  buffer.AudioBytes = static_cast<UINT32>(readSum);
325  buffer.pAudioData = reinterpret_cast<BYTE *>(&m_pBgmBuffer[base]);
326  hr = m_pBgmVoice->SubmitSourceBuffer(&buffer);
327  checkDXResult<XAudioError>(hr, "IXAudio2SourceVoice::SubmitSourceBuffer() failed");
328  //debug::writeLine(L"push back!");
329 
330  m_writePos = (m_writePos + 1) % BgmBufferCount;
331 }
332 
333 
334 Bgm::Bgm(file::Bytes &&ovFileBin) :
335  m_ovFileBin(ovFileBin),
336  m_readPos(0)
337 {
338  // ovfile open (set m_ovFile)
339  ov_callbacks callbacks = { read, seek, close, tell };
340  int ret = ::ov_open_callbacks(this, &m_ovFile, nullptr, 0, callbacks);
341  if (ret != 0) {
342  throw OggVorbisError("ov_open_callbacks() failed", ret);
343  }
344  // auto close at destructor
345  m_ovFp.reset(&m_ovFile);
346 }
347 
348 size_t Bgm::read(void *ptr, size_t size, size_t nmemb, void *datasource)
349 {
350  auto *obj = static_cast<Bgm *>(datasource);
351  ASSERT(obj->m_ovFileBin.size() <= file::FileSizeMax);
352  uint32_t totalSize = static_cast<uint32_t>(obj->m_ovFileBin.size());
353  ASSERT(totalSize >= obj->m_readPos);
354  uint32_t remainSize = totalSize - obj->m_readPos;
355  size_t count = std::min(remainSize / size, nmemb);
356  ::memcpy(ptr, &obj->m_ovFileBin.data()[obj->m_readPos], size * count);
357  obj->m_readPos += static_cast<uint32_t>(size * count);
358  return count;
359 }
360 
361 int Bgm::seek(void *datasource, int64_t offset, int whence)
362 {
363  auto *obj = static_cast<Bgm *>(datasource);
364  ASSERT(offset < file::FileSizeMax);
365  ASSERT(obj->m_ovFileBin.size() <= file::FileSizeMax);
366  uint32_t totalSize = static_cast<uint32_t>(obj->m_ovFileBin.size());
367  switch (whence) {
368  case SEEK_SET:
369  obj->m_readPos = static_cast<uint32_t>(offset);
370  break;
371  case SEEK_CUR:
372  obj->m_readPos += static_cast<uint32_t>(offset);
373  break;
374  case SEEK_END:
375  ASSERT(offset <= 0);
376  obj->m_readPos = static_cast<uint32_t>(totalSize + offset);
377  break;
378  default:
379  return -1;
380  }
381  if (obj->m_readPos > totalSize) {
382  obj->m_readPos = totalSize;
383  return -1;
384  }
385  else if (obj->m_readPos < 0) {
386  obj->m_readPos = 0;
387  return -1;
388  }
389  return 0;
390 }
391 
392 long Bgm::tell(void *datasource)
393 {
394  auto *obj = static_cast<Bgm *>(datasource);
395  return obj->m_readPos;
396 }
397 
398 int Bgm::close(void *datasource)
399 {
400  auto *obj = static_cast<Bgm *>(datasource);
401  return 0;
402 }
403 
404 } // namespace sound
405 } // namespace yappy
std::shared_ptr< BgmResource > BgmResourcePtr
Definition: sound.h:78
Debug utilities.
BgmResourcePtr loadBgm(const wchar_t *path)
Definition: sound.cpp:229
const uint32_t SoundFileSizeMax
Definition: sound.h:14
void writef(const wchar_t *fmt,...) noexcept
Write debug message using format string like printf.
Definition: debug.cpp:103
std::vector< uint8_t > loadFile(const wchar_t *fileName)
Load file from abstract file system.
Definition: file.cpp:103
sh をピクセル座標からUV座標に offset
Definition: Memo.txt:71
#define ASSERT(x)
Assertion which uses debug framework.
Definition: debug.h:18
SeResourcePtr loadSoundEffect(const wchar_t *path)
Definition: sound.cpp:117
void stopAllSoundEffect()
Definition: sound.cpp:181
const uint32_t FileSizeMax
0x7fffffff = 2GiB
Definition: file.h:12
Definition: config.cpp:6
void checkDXResult(HRESULT hr, const std::string &msg)
Definition: exceptions.h:83
void writeLine(const wchar_t *str=L"") noexcept
Write debug string and new line.
Definition: debug.h:64
std::shared_ptr< SeResource > SeResourcePtr
Definition: sound.h:76
Bgm(file::Bytes &&ovFileBin)
Definition: sound.cpp:334
std::vector< uint8_t > Bytes
File byte sequence. Vector of uint8_t.
Definition: file.h:25
const uint32_t BgmBufferSize
Definition: sound.h:18
const uint32_t BgmBufferCount
Definition: sound.h:19
bool isPlayingAnySoundEffect() const
Definition: sound.cpp:162
const uint32_t BgmOvReadSize
Definition: sound.h:17
void playBgm(const BgmResourcePtr &bgm)
Definition: sound.cpp:235
void playSoundEffect(const SeResourcePtr &se)
Definition: sound.cpp:124