SDL2でサウンドエフェクトと音楽を再生します。別途SDL_mixerが必要です。
Table of Contents
1 SDL_mixer
SDL2単体でも音を出すことは可能なのですが、APIに則ったサウンドプログラミングを実装する必要があります。SDL_mixerはサウンドプログラミング部分を実装したライブラリで、簡単なコードで音を出すことができるようになります。
2 SDL_mixerの初期化・終了処理
2.1 Mix_Init
Mix_Initで対応フォーマットで使用するライブラリをロードします。
int Mix_Init(int flags)
mp3の場合はflagsにMIX_INIT_MP3を指定します。他のフォーマットも同時にサポートする場合は|で繋げます。Mix_Initを呼ばなくても動くようなのですが、一応実行します。
2.2 Mix_Quit
Mix_Quitでロードしたライブラリを解放します。
void Mix_Quit()
2.3 Mix_OpenAudio
Mix_OpenAudioでSDL_mixerのAPIを初期化します。
int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)
frequencyは音源の周波数設定です。音源ファイルの周波数は統一する必要があります。formatはSDL_AudioFormatで使用する値のようです。MIX_DEFAULT_FORMATで良いと思います。formatは1でモノラル、2でステレオになります。2で固定でしょう。chunksizeは内部で持つバッファのサイズです。4096とかで良いと思います。
2.4 Mix_CloseAudio
Mix_CloseAudioでSDL_mixerのAPIを終了します。
void Mix_CloseAudio()
3 サウンドエフェクトの再生
WAVを再生します。
3.1 Mix_Chunk構造体
音源ファイルを扱う構造体です。メンバ変数が外部から公開されていますが、基本的に触ることはないでしょう。
/* The internal format for an audio chunk */
typedef struct Mix_Chunk {
int allocated;
Uint8 *abuf;
Uint32 alen;
Uint8 volume; /* Per-sample volume, 0-128 */
} Mix_Chunk;
3.2 Mix_LoadWAV
Mix_LoadWAVでファイル名からMix_Chunk構造体を生成します。
Mix_Chunk *Mix_LoadWAV(char *file)
Mix_LoadWAV_RWとMix_QuickLoad_WAVもあり、メモリ上に展開された音源からもMix_Chunk構造体を生成できます。
3.3 Mix_FreeChunk
Mix_FreeChunkでMix_Chunk構造体を解放します。
void Mix_FreeChunk(Mix_Chunk *chunk)
3.4 Mix_AllocateChannels
使用するチャネルの総数を設定します。-1を指定した場合は現在の使用できるチャネルの総数を取得します。
int Mix_AllocateChannels(int numchans)
本APIを呼ばない場合、使用できるチャネルの総数は8になるようです。
3.5 Mix_PlayChannel
Mix_PlayChannelでMix_Chunk構造体をチャネルに割り当てて再生します。
int Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops)
channelに-1を指定した場合は、停止しているチャネルを割り当て、戻り値として返します。特定のチャネルを特定の音源に割り当てたい場合(例えば、雨の音をループ再生したい場合)、Mix_ReserveChannelsでチャネル番号を予約しておき、Mix_PlayChannelで予約したチャネル番号を指定すれば良いでしょう。
loopsに-1を設定した場合は無限に音源がループします。クリック音等のサウンドエフェクトは1を、雨などの流れ続ける音は-1を設定すれば良いでしょう。
3.6 Mix_HaltChannel
Mix_HaltChannelで再生されているチャネルを停止します。-1を指定した場合はすべてのチャネルを停止します。
int Mix_HaltChannel(int channel)
3.7 Mix_Playing
Mix_Playingでチャネルが再生中かを確認します。-1を指定した場合は再生中のチャネルの総数を取得します。
int Mix_Playing(int channel)
3.8 Mix_Volume
Mix_Volumeでチャネルの音量を設定します。channelに-1を指定した場合は全チャネルの音量を設定します。
int Mix_Volume(int channel, int volume)
Mix_Chunk構造体単位で音量を設定するMix_VolumeChunkもあります。
3.9 Mix_ChannelFinished
Mix_ChannelFinishedで音源の再生が停止した際のコールバック関数を登録します。
void Mix_ChannelFinished(void (*channel_finished)(int channel))
本APIを使用すれば、音源を鳴らす箇所は再生だけをケアすればよくなります。例えば、クリック時のハンドラにて音源を慣らしておき、コールバック関数でMix_Chunk構造体を解放しておくとコードが簡単になります。
4 音楽の再生
mp3を再生します。
4.1 Mix_Music構造体
音楽を扱う構造体です。外部からは隠蔽されています。
/* The internal format for a music chunk interpreted via mikmod */ typedef struct _Mix_Music Mix_Music;
Mix_Chunk構造体とは異なり、同時に再生できる音楽はひとつのみです。
4.2 Mix_LoadMUS
Mix_LoadMUSでMix_Music構造体を生成します。
Mix_Music *Mix_LoadMUS(const char *file)
Mix_Chunk構造体とは異なり、Mix_Musicはファイルストリーム経由でしか生成できません。複数ファイルをまとめたリソースファイルから音楽を再生したい場合はSDL2のコードを改造する必要があります(各プラットフォーム向けのコードを含めるので手間がかかる)。
リソースファイルを用いて、外部から音源をできる限り隠したい場合は、WAV形式にしてMix_Chunk構造体を使うべきです。
4.3 Mix_FreeMusic
Mix_FreeMusicでMix_Music構造体を破棄します。
void Mix_FreeMusic(Mix_Music *music)
4.4 Mix_PlayMusic
Mix_PlayMusicで音楽を再生します。
int Mix_PlayMusic(Mix_Music *music, int loops)
loopsでループ回数を設定します。-1の場合は無限ループします。
4.5 Mix_HaltMusic
Mix_HaltMusicで音楽を停止します。
int Mix_HaltMusic()
4.6 Mix_PlayingMusic
Mix_PlayingMusicで音楽が再生中かどうかを確認します。
int Mix_PlayingMusic()
4.7 Mix_VolumeMusic
Mix_VolumeMusicで音楽のボリュームを設定します。
int Mix_VolumeMusic(int volume)
-1を指定した場合は現在のボリュームを取得します。
5 サンプルプログラム
サウンドエフェクトと音楽を鳴らします。別途、周波数が22050のWAVファイルa.wavとMP3ファイルa.mp3を用意してください。
5.1 Soundクラス
サウンドエフェクトを扱うクラスです。チャネルとクラスへのポインタをマップとして保持しておき、クラスの解放は音源が停止した際にchannelFinish関数で実行します。
#ifndef __MYSDL_SOUND_H
#define __MYSDL_SOUND_H
#include <Mutex.h>
#include <SDL_mixer.h>
#include <string>
#include <map>
namespace mysdl {
extern void channelFinished(int);
class Sound {
private:
static std::map<int, Sound *> gMap;
static Mutex gMutex;
Mix_Chunk *mChunk;
int mChannel;
public:
static int play(const std::string &fileName)
{
Sound *sound = new Sound(fileName);
int channel;
gMutex.lock();
channel = sound->__play();
if (channel >= 0)
gMap[channel] = sound;
gMutex.unlock();
return channel;
}
static void stop()
{
gMutex.lock();
for (std::map<int, Sound *>::iterator i = gMap.begin(),
e = gMap.end(); i != e; ++i)
if (i->second != nullptr)
i->second->__stop();
gMutex.unlock();
}
static void stop(int channel)
{
Sound *sound;
gMutex.lock();
sound = gMap[channel];
gMap[channel] = nullptr;
gMutex.unlock();
delete sound;
}
Sound(const std::string &fileName)
{
mChunk = Mix_LoadWAV(fileName.c_str());
}
~Sound()
{
Mix_FreeChunk(mChunk);
}
int __play()
{
return Mix_PlayChannel(-1, mChunk, 0);
}
void __stop()
{
if (mChannel >= 0) {
Mix_HaltChannel(mChannel);
mChannel = -1;
}
}
bool __isPlaying()
{
return mChannel >= 0 && Mix_Playing(mChannel);
}
};
};
#endif /** __MYSDL_SOUND_H */
5.2 Musicクラス
音楽を扱うクラスです。音楽は同時にひとつしか流せない為、グローバルなメソッドで再生と停止します。
#ifndef __MYSDL_MUSIC_H
#define __MYSDL_MUSIC_H
#include <Mutex.h>
#include <SDL_mixer.h>
#include <string>
namespace mysdl {
class Music {
private:
static Music *gMusic;
static Mutex gMutex;
Mix_Music *mMusic;
public:
static bool play(const std::string &fileName)
{
bool ret;
gMutex.lock();
if (gMusic != nullptr) {
if (gMusic->__isPlaying())
gMusic->__stop();
delete gMusic;
}
gMusic = new Music(fileName);
ret = gMusic->__play();
gMutex.unlock();
return ret;
}
static void stop()
{
gMutex.lock();
if (gMusic != nullptr) {
if (gMusic->__isPlaying())
gMusic->__stop();
delete gMusic;
}
gMusic = nullptr;
gMutex.unlock();
}
static bool isPlaying()
{
bool ret;
gMutex.lock();
ret = gMusic != nullptr ? gMusic->__isPlaying() : false;
gMutex.unlock();
return ret;
}
Music(const std::string &fileName)
{
mMusic = Mix_LoadMUS(fileName.c_str());
}
~Music()
{
Mix_FreeMusic(mMusic);
}
bool __play()
{
return Mix_PlayMusic(mMusic, -1) == 0;
}
void __stop()
{
if (isPlaying())
Mix_HaltMusic();
}
bool __isPlaying()
{
return Mix_PlayingMusic();
}
};
};
#endif /** __MYSDL_MUSIC_H */
5.3 main.cc
--- main.cc.org 2015-05-19 07:19:53.000000000 +0900
+++ main.cc 2015-05-19 07:18:03.000000000 +0900
@@ -5,6 +5,8 @@
#include <StringObject.h>
#include <PixelObject.h>
#include <Font.h>
+#include <Sound.h>
+#include <Music.h>
#include <SDL.h>
using namespace mysdl;
@@ -118,6 +120,27 @@ static bool input()
return false;
}
+static bool initializeAudio()
+{
+ if (Mix_Init(MIX_INIT_MP3) < 0)
+ return false;
+ if (Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 4096) < 0)
+ goto err;
+ Mix_ChannelFinished(channelFinished);
+ return true;
+ err:
+ Mix_Quit();
+ return false;
+}
+
+static void finalizeAudio()
+{
+ Sound::stop();
+ Music::stop();
+ Mix_CloseAudio();
+ Mix_Quit();
+}
+
int main(int argc, char *argv[])
{
Window window(SDL_WINDOW_TITLE, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT);
@@ -126,15 +149,22 @@ int main(int argc, char *argv[])
Font::initialize();
+ if (!initializeAudio())
+ return 1;
+
gSprite = new SpriteObject("a.png", 10, 10, 100, 100);
gRenderer->send(gSprite);
+ Sound::play("a.wav");
+ Music::play("a.mp3");
+
while (1)
if (input())
break;
Font::finalize();
+ finalizeAudio();
//thread.stop();
return 0;
}