Part 32: Sound
Downloads
The sound with Qt
#define TIMER_PERIOD 17
#define SAMPLE_RATE 6000
#define CHANNELS 2
#define SAMPLE_SIZE 16
#define SAMPLE_BYTES (SAMPLE_SIZE / 8)
#define MIXER_COEF 4 // number of simultaneous sounds
CSoundManager::CSoundManager()
{
memset(mBuffer, 0, SND_BUFFER_SIZE);
QAudioFormat format;
format.setSampleRate(SAMPLE_RATE);
format.setChannelCount(CHANNELS);
format.setSampleSize(SAMPLE_SIZE);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo device = QAudioDeviceInfo::defaultOutputDevice();
QAudioDeviceInfo info(device);
if (info.isFormatSupported(format) == false)
format = info.nearestFormat(format);
mAudioOutput = new QAudioOutput(device, format);
mPushTimer = new QTimer();
connect(mPushTimer, SIGNAL(timeout()), SLOT(pushTimerExpired()));
mOutput = mAudioOutput->start();
mPushTimer->start(TIMER_PERIOD);
}
You can see at the end that we initialize a timer that will be used to feed regularly the AudioOutput with
The sound list
class CSound
{
public:
CSound(CVec2 pos, const char* fileName, int loops);
[...]
CVec2 mPos;
std::string mFileName;
private:
[...]
uint32_t mCurrentPos;
int mLoops;
};
class CSoundManager : public QObject
{
Q_OBJECT
public:
[...]
void play(CVec2 pos, const char *fileName, int loops = 1);
void stop(CVec2 pos, const char *fileName);
private:
[...]
std::vector<CSound> mSounds;
[...]
};
The variables used for each sound are:
void CSoundManager::play(CVec2 pos, const char* fileName, int loops)
{
std::vector<CSound>::iterator it;
for (it = mSounds.begin(); it !=mSounds.end(); ++it)
if (it->mPos == pos && it->mFileName == fileName)
break;
if (it == mSounds.end())
{
CSound snd(pos, fileName, loops);
mSounds.push_back(snd);
}
}
The stop function will be used to remove a looping sound from the list.
void CSoundManager::stop(CVec2 pos, const char *fileName)
{
std::vector<CSound>::iterator it;
for (it = mSounds.begin(); it !=mSounds.end();)
{
if (it->mPos == pos && it->mFileName == fileName)
it = mSounds.erase(it);
else
it++;
}
}
Sending the samples
void CSoundManager::pushTimerExpired()
{
if (mAudioOutput && mAudioOutput->state() != QAudio::StoppedState)
{
int len = SAMPLE_BYTES * CHANNELS * SAMPLE_RATE * TIMER_PERIOD / 1000;
// clear the sound buffer
memset(mBuffer, 0, len);
Then we sort the sounds by their distance from the player.
if (interface.isGamePaused() == false)
{
// sort the sounds
std::vector<CSound*> sorted;
for (size_t i = 0; i < mSounds.size(); ++i)
sorted.push_back(&mSounds[i]);
for (int i = 0; i < (int)mSounds.size() - 1; ++i)
for (int j = i + 1; j < (int)mSounds.size(); ++j)
if (sorted[j]->getDistance() < sorted[i]->getDistance())
{
CSound* temp = sorted[i];
sorted[i] = sorted[j];
sorted[j] = temp;
}
Now that the sounds are sorted, we will play only the 4 nearest ones and we
// play the 4 nearest sounds (the others' samples are skipped)
int leftVolume, rightVolume;
for (size_t i = 0; i < mSounds.size(); ++i)
{
if (i < MIXER_COEF)
{
mSounds[i].getVolumes(&leftVolume, &rightVolume);
mSounds[i].getData(mBuffer, len, leftVolume, rightVolume);
}
else
{
mSounds[i].skipSamples(len);
}
}
}
Now the buffer is ready, we send it to the output.
mOutput->write((char *)mBuffer, len);
And finally, we remove from the list all the sounds that are finished.
// delete finished sounds
std::vector<CSound>::iterator it;
for (it = mSounds.begin(); it != mSounds.end();)
{
if (it->isFinished() == true)
it = mSounds.erase(it);
else
it++;
}
}
}
Mixing the samples
void CSound::getData(int16_t* dest, int len, int leftVolume, int rightVolume)
{
for (int i = 0; i < len / (SAMPLE_BYTES * CHANNELS); ++i)
{
int sample = (int)getSample() - 128;
*dest++ += sample * leftVolume / MIXER_COEF;
*dest++ += sample * rightVolume / MIXER_COEF;
mCurrentPos++;
}
}
The source datas of the samples comes from an 8 bits WAV file.
uint8_t CSound::getSample()
{
uint8_t* mem = (uint8_t*)fileCache.getFile(mFileName).mem;
uint32_t size = *(uint32_t*)&mem[WAV_HEADER - 4];
if (isFinished() == false)
return mem[WAV_HEADER + (mCurrentPos % size)];
else
return 128;
}
Spatialization
void CSound::getVolumes(int* left, int* right)
{
CVec2 pos = player.getLocalFromPos(mPos);
If the sound is on the same tile as us, we set the volume to the maximum.
if (pos == CVec2(0, 0))
{
*left = 256;
*right = 256;
return;
}
Now we will set the the left and the right volumes according to the sound position.
// calc panning
float angle = atan2(-pos.y, pos.x);
float l, r;
if (pos.x < 0)
{
l = 1.0f;
r = fabs(sin(angle));
}
else
{
l = fabs(sin(angle));
r = 1.0f;
}
Then we want to lower the sound when it is far from us
// calc distance
float vol = (5.0f - pos.length()) / 5.0f;
if (vol < 0.0f)
vol = 0.0f;
l *= vol;
r *= vol;
And finally we return the values of the volumes in a range from 0 to 256.
// return values
*left = (int)(l * 256.0f);
*right = (int)(r * 256.0f);
}
I added a few sounds in the game - hitting a wall, attacking, pressing a switch, opening a door - so that you can
The save bug