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