So yesterday and today, I bit the bullet and started working on compiling the game for windows. I wanted to do this before the project got too much further along so I didn’t write any code that would have serious implications on windows. I must say, I’m amazed at how much trouble I didn’t have with the rendering system. The levels/screens load up and look great! I did have some issues with visual studio that I chalk up to lack of experience in windows dev on my part. Namely getting the builds to be 64-bit instead of the default 32, SSE3 operator overload stuff, and getting debug builds to forego some of the extremely slow settings that make the engine run at a crawl. Once I took care of all that, my little game was running on windows like a champ, complete with same stellar OpenGL 3.3 performance I get on mac. All was well… that is except for one little tiny issue. The sound.
One thing that became apparent almost right away was that OpenAL support on windows is in a terrible state. Counting on it to just be there like OpenGL is a no no.. and installing your own via something like OpenAL soft gives pretty poor support. I realized that I had to do something I’ve never done before, and that’s directly use a DirectX subsystem. After some quick googling I discovered what I need can be done using a combination of xAudio2 and X3DAudio. Both of these systems come with DirectX and the DX SDK so I was already set up to use em.
So basically, the plan on windows was pretty simple: since I already have all my 3D sound calls abstracted out in my SoundManager class, I all I had to do is re-implement the stuff that OpenAL handled using this new xAudio2 stuff.. and pray that it played nice along side SDL_Mixer which I use for mp3 music. More or less, things went relatively painless and I was able to find an xAudio2/X3DAudio analog for just about everything I was doing in OpenAL EXCEPT for a little issue I had with source playback.
On mac, I’m able to play a source overtop of itself as many times as I want to. On windows, this seems to be a no-go. So for win, I had to manually pre-allocate pools of sources for the sounds that I would be playing rapidly such as bullet ricochet sounds and typewriter keystrokes and ensure that I return an available source whenever I ask to play that sound. That process more or less worked out just fine.
After all that, we have my reworked soundmanager class for windows. The code is below for anyone who wants to learn from it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
//
// SoundManager.h
// Gangster Driveby
//
//
#ifndef __Gangster_Driveby__SoundManager__
#define __Gangster_Driveby__SoundManager__
#include <windows.h>
#include <xaudio2.h>
#include <x3daudio.h>
#include <vector>
#include <iostream>
#include "VecTypes.h"
#include "Wave.h"
#include "SDL_mixer.h"
class SoundManager
{
public:
enum Effect
{
Engine = 0,
Typewriter1,
Typewriter2,
Typewriter3,
Typewriter4,
Typewriter5,
TypewriterSpace,
TypewriterDing,
TypewriterFeed,
TommyGun,
TommyGunLoop,
GlassBreak,
BulbBreak,
BulletHoleGlass,
DramaticBoom,
PaperIn,
PaperOut,
OceanClose,
WavesAmbience,
//MUST be last
LongMusic
};
enum Music
{
DarkFlashes = 0,
NightDocksSax,
DontGoWayNobody
};
class MultiSource
{
friend class SoundManager;
public:
MultiSource(IXAudio2 *engine, Wave *wave, int maxSources = 1)
{
for(int i = 0; i < maxSources; i++)
{
IXAudio2SourceVoice *source = nullptr;
if(FAILED(engine->CreateSourceVoice(&source, wave->wf())))
{
std::cerr << "source creation error!" << std::endl;
}
sources.push_back(source);
}
ZeroMemory(&emitter, sizeof(emitter));
emitter.ChannelCount = 1; //not 100% on this
emitter.CurveDistanceScaler = FLT_MIN;
ZeroMemory(&dspSettings, sizeof(dspSettings));
FLOAT32 *dspMatrix = nullptr;
XAUDIO2_DEVICE_DETAILS details;
engine->GetDeviceDetails(0, &details);
dspMatrix = new FLOAT32[details.OutputFormat.Format.nChannels];
dspSettings.SrcChannelCount = 1;
dspSettings.DstChannelCount = details.OutputFormat.Format.nChannels;
dspSettings.pMatrixCoefficients = dspMatrix;
}
///Returns an available source
IXAudio2SourceVoice *getSource();
///Updates and calculates 3D sound stuff
void update3D(SoundManager *sm);
//One or many sources per sound
std::vector<IXAudio2SourceVoice *> sources;
//3D sound emitter (if needed for this source)
X3DAUDIO_EMITTER emitter;
X3DAUDIO_DSP_SETTINGS dspSettings;
bool is3D = false, is3DUpdated = false;
//Useful to keep track of this since 3D audio calculates its own
float basePitch = 1.0f;
};
SoundManager();
~SoundManager();
static SoundManager *manager();
inline bool isActive() { return (engine != nullptr); }
void setEffectBasicProperties(Effect effect, float gain, float pitch=1.0f, bool looping=false);
void setEffectGain(Effect effect, float gain);
void setEffectPitch(Effect effect, float pitch);
void setEffectSpatialProperties(Effect effect, float referenceDistance, float maxDistance, float rollOffFactor=0.0f,
float3 velocity = float3::zero, float3 direction = float3::zero);
void setEffectPosition(Effect effect, float3 pos);
void setListenerPosition(float3 pos, float3 orientation[]);
void playEffect(Effect effect);
void stopEffect(Effect effect);
void playSong(Music song);
void stopSong();
void setSongVolume(float volume);
void setSongPosition(double pos);
float getSongVolume();
void loadLongMusicEffect(const char *fn);
void unloadLongMusicEffect();
inline void setSoundAvailable(bool b) { soundAvailable = b; }
void update3D();
private:
void loadSoundEffects();
IXAudio2 *engine = nullptr;
IXAudio2MasteringVoice *master = nullptr;
XAUDIO2_DEVICE_DETAILS deviceDetails;
X3DAUDIO_HANDLE engine3d;
X3DAUDIO_LISTENER listener;
std::vector<MultiSource *> sources;
std::vector<Wave *> buffers;
Wave *longMusicBuffer = nullptr;
MultiSource *longMusicSource = nullptr;
Mix_Music *currentSong = nullptr;
bool soundAvailable = true;
float currentSongVolume = 1.0f;
};
#endif /* defined(__Gangster_Driveby__SoundManager__) */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
//
// SoundManager.cpp
// Gangster Driveby
//
// Created by Mike Farrell on 11/14/14.
// Copyright (c) 2014 Mike Farrell. All rights reserved.
//
#include <cstdlib>
#include <iostream>
#include "PortableSoundInfo.h"
#include "SoundManager.h"
#include "SDL_mixer.h"
static SoundManager *currentSoundManager = nullptr;
static const char *sounds[] = {
"eng_1_mid_loop.wav",
"type1.wav",
"type2.wav",
"type3.wav",
"type4.wav",
"type5.wav",
"type_space.wav",
"type_ding.wav",
"type_feed.wav",
"tommy.wav",
"tommy_loop.wav",
"glassbreak.wav",
"bulb_break.wav",
"bullethole_glass.wav",
"dramatic_boom.wav",
"paper_in.wav",
"paper_out.wav",
"ocean_close.wav",
"waves_ambience.wav"
};
static const int soundSourceCounts[] = {
1,
8, 8, 8, 8, 8, 8,
1, 1,
8, 1,
8, 8, 8,
1, 1, 1, 1, 1
};
static const char *songs[] = {
/*....*/
};
static const int numSounds = sizeof(sounds)/sizeof(char *);
//static const int numSongs = sizeof(songs)/sizeof(char *);
using namespace std;
SoundManager::SoundManager()
{
//create the engine
if(!FAILED(XAudio2Create(&engine)))
{
//create the mastering voice
if(!FAILED(engine->CreateMasteringVoice(&master)))
{
//create 3D sound engine
DWORD dwChannelMask;
if(!FAILED(engine->GetDeviceDetails(0, &deviceDetails)))
{
dwChannelMask = deviceDetails.OutputFormat.dwChannelMask;
X3DAudioInitialize(dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, engine3d);
ZeroMemory(&listener, sizeof(listener));
}
else
{
cerr << "3D sound init failed!" << endl;
}
//and load sound fx
loadSoundEffects();
}
}
else
{
cerr << "Could not initialize DirectSound for sound!" << endl;
}
currentSoundManager = this;
}
SoundManager::~SoundManager()
{
if(engine)
engine->Release();
for(Wave *wave : buffers) if(wave)
delete wave;
for(MultiSource *ms : sources) if(ms)
delete ms;
if(currentSong)
Mix_FreeMusic(currentSong);
}
SoundManager *SoundManager::manager()
{
return currentSoundManager;
}
void SoundManager::loadSoundEffects()
{
auto sz = numSounds;
for(int i = 0; i < numSounds; i++)
{
Wave *buffer = new Wave();
if(!buffer->load(sounds[i]))
{
cerr << "shilt" << endl;
}
int maxSources = soundSourceCounts[i];
MultiSource *source = new MultiSource(engine, buffer, maxSources);
buffers.push_back(buffer);
sources.push_back(source);
}
//long music placeholder
sources.push_back(nullptr);
buffers.push_back(nullptr);
}
void SoundManager::update3D()
{
for(auto s : sources) if(s)
{
s->update3D(this);
}
}
void SoundManager::loadLongMusicEffect(const char *fn)
{
if(longMusicBuffer)
unloadLongMusicEffect();
PortableSoundInfo sound(fn);
longMusicBuffer = new Wave();
if(!longMusicBuffer->load(sound))
{
cerr << "long music load failed!" << endl;
}
longMusicSource = new MultiSource(engine, longMusicBuffer, 1);
sources[(int)LongMusic] = longMusicSource;
buffers[(int)LongMusic] = longMusicBuffer;
}
void SoundManager::unloadLongMusicEffect()
{
delete longMusicBuffer;
longMusicBuffer = nullptr;
delete longMusicSource;
longMusicSource = nullptr;
}
void SoundManager::setEffectGain(SoundManager::Effect effect, float gain)
{
const float vol = gain;
auto source = sources[(int)effect];
for(auto s : source->sources)
{
s->SetVolume(vol);
}
}
void SoundManager::setEffectBasicProperties(SoundManager::Effect effect, float gain, float pitch, bool looping)
{
setEffectGain(effect, gain);
if(pitch != 0.0f)
{
setEffectPitch(effect, pitch);
}
if(!looping)
{
buffers[(int)effect]->xaBuffer()->LoopCount = 0;
}
else
{
buffers[(int)effect]->xaBuffer()->LoopCount = XAUDIO2_MAX_LOOP_COUNT;
}
}
void SoundManager::setEffectPitch(SoundManager::Effect effect, float pitch)
{
auto source = sources[(int)effect];
for(auto s : source->sources)
{
const float ratio = pitch;
s->SetFrequencyRatio(ratio);
}
//mm.. mmmm, pitch!
source->basePitch = pitch;
}
void SoundManager::setEffectSpatialProperties(SoundManager::Effect effect, float referenceDistance, float maxDistance, float rollOffFactor,
float3 velocity, float3 direction)
{
//stereo tracks get a horrible sound blasting result when you try to do this
//lets just do what openAL does, which is silently fail
if(buffers[(int)effect]->wf()->nChannels > 1)
{
return;
}
//not reversible for now
auto source = sources[(int)effect];
source->is3D = true;
source->emitter.Velocity = { velocity.x, velocity.y, velocity.z };
if(direction != float3::zero)
{
source->emitter.OrientFront = { direction.x, direction.y, direction.z };
source->emitter.OrientTop = listener.OrientTop;
}
//a "max distance" isn't available in x3d audio it seems..
float distanceScalar = referenceDistance - rollOffFactor*2.5;
source->emitter.CurveDistanceScaler = max(0.0f, distanceScalar);
}
void SoundManager::setEffectPosition(Effect effect, float3 pos)
{
auto source = sources[(int)effect];
source->emitter.Position = { pos.x, pos.y, pos.z };
}
void SoundManager::setListenerPosition(float3 pos, float3 orientation[])
{
listener.Position = { pos.x, pos.y, pos.z };
listener.OrientFront = { orientation[0].x, orientation[0].y, orientation[0].z };
listener.OrientTop = { orientation[1].x, orientation[1].y, orientation[1].z };
}
void SoundManager::playEffect(SoundManager::Effect effect)
{
auto ms = sources[(int)effect];
auto source = ms->getSource();
//ensure initial 3D calculations done if requested
if(ms->is3D && !ms->is3DUpdated)
{
ms->update3D(this);
}
source->Start();
source->SubmitSourceBuffer(buffers[(int)effect]->xaBuffer());
}
void SoundManager::stopEffect(SoundManager::Effect effect)
{
auto source = sources[(int)effect];
for(auto s : source->sources)
{
s->Stop();
}
}
void SoundManager::playSong(SoundManager::Music song)
{
if(soundAvailable)
{
if(currentSong)
Mix_FreeMusic(currentSong);
currentSong = Mix_LoadMUS(songs[(int)song]);
Mix_PlayMusic(currentSong, -1);
}
}
void SoundManager::stopSong()
{
if(soundAvailable)
{
Mix_HaltMusic();
}
}
void SoundManager::setSongVolume(float volume)
{
currentSongVolume = volume;
if(soundAvailable)
{
Mix_VolumeMusic((int)roundf(volume*MIX_MAX_VOLUME));
}
}
void SoundManager::setSongPosition(double pos)
{
if(soundAvailable)
{
Mix_SetMusicPosition(pos);
}
}
float SoundManager::getSongVolume()
{
return currentSongVolume;
}
IXAudio2SourceVoice *SoundManager::MultiSource::getSource()
{
auto isPlaying = [](IXAudio2SourceVoice *source)->bool {
XAUDIO2_VOICE_STATE state;
ZeroMemory(&state, sizeof(XAUDIO2_VOICE_STATE));
source->GetState(&state);
return (state.BuffersQueued != 0);
};
int i = 0;
while(i < sources.size() && isPlaying(sources[i]))
{
i++;
}
if(i == sources.size())
{
if(sources.size() > 1)
{
#ifdef DEBUG
cout << "Warning: sound queue will occur (this will suck)" << endl;
#endif
}
return sources[0];
}
return sources[i];
}
void SoundManager::MultiSource::update3D(SoundManager *sm)
{
if(is3D)
{
//X3DAUDIO_CALCULATE_LPF_DIRECT ?
X3DAudioCalculate(sm->engine3d, &sm->listener, &emitter, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER, &dspSettings);
for(auto s : sources)
{
s->SetOutputMatrix(sm->master, 1, sm->deviceDetails.OutputFormat.Format.nChannels, dspSettings.pMatrixCoefficients);
s->SetFrequencyRatio(basePitch * dspSettings.DopplerFactor);
}
is3DUpdated = true;
}
} |
All of the multiple source stuff is essentially handled by my inner MultiSource class which handles the souce pools for sounds that need em. I found that I never needed more than 8 sources per sound. I also had to add a new update3D method to update the 3D sound calculations and call it once every other frame or so (which wasn’t needed in OpenAL since it was done automatically).
Not shown here is a Wave class which essentially is a slightly modified version of the one from this tutorial. That class basically handles wav file loading for me and manages the sound buffer data internally.
That’s just about it. Everything else was pretty straightforward.