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; }