Partie 32: Le son
Téléchargements
Le son avec 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 // nombre de sons simultanés
#define WAV_HEADER 44 // taille de l'entête d'un fichier ".wav"
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);
}
Vous pouvez voir qu'à la fin on initialise un timer qui sera utilisé pour alimenter régulièrement mOutput avec nos
La liste de sons
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;
[...]
};
Les variables utilisées pour chaque son sont:
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);
}
}
La fonction stop() sera utilisée pour retirer un son qui boucle de la liste.
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++;
}
}
Envoyer les samples
void CSoundManager::pushTimerExpired()
{
if (mAudioOutput && mAudioOutput->state() != QAudio::StoppedState)
{
int len = SAMPLE_BYTES * CHANNELS * SAMPLE_RATE * TIMER_PERIOD / 1000;
// efface le buffer son
memset(mBuffer, 0, len);
Puis on trie les sons en fonction de leur distance au joueur.
if (interface.isGamePaused() == false)
{
// trie les sons
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;
}
Maintenant que les sons sont triés, on va jouer seulement les 4 plus proches et on va sauter les samples des
// joue les 4 sons les plus proches (on saute les autres samples)
int leftVolume, rightVolume;
for (size_t i = 0; i < sorted.size(); ++i)
{
if (i < MIXER_COEF)
{
sorted[i]->getVolumes(&leftVolume, &rightVolume);
sorted[i]->getData(mBuffer, len, leftVolume, rightVolume);
}
else
{
sorted[i]->skipSamples(len);
}
}
}
Maintenant que le buffer est prêt, on l'envoie à mOutput.
mOutput->write((char *)mBuffer, len);
Et finalement, on retire de la liste tous les sons qui sont terminés.
// efface les sons terminés
std::vector<CSound>::iterator it;
for (it = mSounds.begin(); it != mSounds.end();)
{
if (it->isFinished() == true)
it = mSounds.erase(it);
else
it++;
}
}
}
Mixer les 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++;
}
}
Les données source des samples viennent d'un fichier WAV 8 bits.
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;
}
Spatialisation
void CSound::getVolumes(int* left, int* right)
{
CVec2 pos = player.getLocalFromPos(mPos);
Si le son est dans la même case que nous, on met le volume au maximum.
if (pos == CVec2(0, 0))
{
*left = 256;
*right = 256;
return;
}
Ensuite, on va mettre les volumes gauche et droit en fonction de la position du son.
// calcule la balance
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;
}
Ensuite, on veut réduire le son quand il est loin de nous
// calcule la distance
float vol = (5.0f - pos.length()) / 5.0f;
if (vol < 0.0f)
vol = 0.0f;
l *= vol;
r *= vol;
Et finalement, on renvoie les valeurs des volumes dans un intervalle de 0 à 256.
// retourne les valeurs
*left = (int)(l * 256.0f);
*right = (int)(r * 256.0f);
}
J'ai ajouté quelques sons dans le jeu (rentrer dans un mur, attaquer, presser un bouton, ouvrir une porte) pour que
Le bug de sauvegarde