RythmGame/SimpleGame/src/Source/AudioEmitter.cpp
2025-06-16 17:38:21 +02:00

398 lines
17 KiB
C++
Raw Blame History

#include "AudioEmitter.hpp"
#include <iostream>
#include <numeric>
#include <random>
#include <utility>
#include <vector>
void ERRCHECK(FMOD_RESULT result) {
if (result != FMOD_OK) {
std::cout << "FMOD error: " << FMOD_ErrorString(result) << std::endl;
exit(-1);
}
}
auto fmodSoundDeleter = [](FMOD::Sound *sound) {
if (sound) {
sound->release();
}
};
AudioEmitter::AudioEmitter() {
FMOD::System *rawSystem = nullptr;
ERRCHECK(FMOD::System_Create(&rawSystem));
system.reset(rawSystem);
ERRCHECK(system->init(512, FMOD_INIT_NORMAL, nullptr));
std::vector<FMOD::Sound *> rawChords(7);
ERRCHECK(system->createSound("media/chords/variation1/C.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[0]));
ERRCHECK(system->createSound("media/chords/variation1/D.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[1]));
ERRCHECK(system->createSound("media/chords/variation1/E.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[2]));
ERRCHECK(system->createSound("media/chords/variation1/F.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[3]));
ERRCHECK(system->createSound("media/chords/variation1/G.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[4]));
ERRCHECK(system->createSound("media/chords/variation1/A.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[5]));
ERRCHECK(system->createSound("media/chords/variation1/B.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords[6]));
std::vector<FMOD::Sound*> rawChords2(7);
ERRCHECK(system->createSound("media/chords/variation2/C.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[0]));
ERRCHECK(system->createSound("media/chords/variation2/D.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[1]));
ERRCHECK(system->createSound("media/chords/variation2/E.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[2]));
ERRCHECK(system->createSound("media/chords/variation2/F.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[3]));
ERRCHECK(system->createSound("media/chords/variation2/G.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[4]));
ERRCHECK(system->createSound("media/chords/variation2/A.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[5]));
ERRCHECK(system->createSound("media/chords/variation2/B.mp3", FMOD_LOOP_OFF, nullptr,
&rawChords2[6]));
for (int i = 0; i < 7; i += 1) {
chords.push_back(std::unique_ptr<FMOD::Sound>(rawChords[i]));
}
for (int i = 0; i < 7; i += 1) {
chords.push_back(std::unique_ptr<FMOD::Sound>(rawChords2[i]));
}
std::vector<FMOD::Sound *> rawNotes(15);
ERRCHECK(system->createSound("media/notes/A1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[0]));
ERRCHECK(system->createSound("media/notes/B1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[1]));
ERRCHECK(system->createSound("media/notes/C1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[2]));
ERRCHECK(system->createSound("media/notes/D1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[3]));
ERRCHECK(system->createSound("media/notes/E1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[4]));
ERRCHECK(system->createSound("media/notes/F1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[5]));
ERRCHECK(system->createSound("media/notes/G1.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[6]));
ERRCHECK(system->createSound("media/notes/A2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[7]));
ERRCHECK(system->createSound("media/notes/B2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[8]));
ERRCHECK(system->createSound("media/notes/C2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[9]));
ERRCHECK(system->createSound("media/notes/D2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[10]));
ERRCHECK(system->createSound("media/notes/E2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[11]));
ERRCHECK(system->createSound("media/notes/F2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[12]));
ERRCHECK(system->createSound("media/notes/G2.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[13]));
ERRCHECK(system->createSound("media/notes/A3.mp3", FMOD_LOOP_OFF, nullptr,
&rawNotes[14]));
for (int i = 0; i < 15; i += 1) {
notes.push_back(std::unique_ptr<FMOD::Sound>(rawNotes[i]));
}
index_note = firstNote();
rythmes = {
{0, 4},
{0, 2, 4, 6},
{0, 2, 3, 4, 6},
{0, 2, 4, 5, 6},
{0, 1, 2, 3, 4, 5, 6, 7},
{0, 0.5, 2, 2.5, 4, 4.5, 6, 6.5},
{0, 2, 2.5, 4, 6, 6.5},
{0, 1.5, 3, 4.5, 6},
{0, 1, 2.5, 4, 5.5, 7},
{0, 1.5, 3, 4.5, 6},
{0, 1, 3, 4.5, 6},
{0, 0.5, 1.5, 2, 3.5, 4, 5.5, 6},
{0, 1.5, 3, 4, 5.5, 7},
{0, 2, 3.5, 5, 6.5},
{0, 0.5, 2, 2.5, 4, 4.5, 6, 6.5},
{0, 1.5, 3, 4.5, 6, 7.5},
{0, 0.5, 1.5, 3, 3.5, 5, 6},
{0, 0.75, 1.5, 3, 4, 5.5, 7},
{0, 0.5, 1.5, 2.5, 4, 4.5, 6, 7},
{0, 0.5, 1.5, 2, 3.5, 4.5, 6, 7},
{0, 1.5, 3, 4, 5.5, 7},
{0, 1.5, 3, 3.5, 5, 6.5, 7.5},
{0.5, 1.5, 3, 4.5, 6.5, 7},
{0, 0.5, 1.5, 2.5, 3.5, 4.5, 6},
{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7},
{0,1,1.5,2,2.5,3,3.5,4,5,6,7}
};
//matrice r<>cup<75>r<EFBFBD>es sur https://musiquealgorithmique.fr/chaines-de-markov-2/
markov_matrix_chords = {{0, 0.10, 0.00, 0.40, 0.35, 0.15, 0.00},
// Depuis ii (1)
{0.10, 0, 0.00, 0.25, 0.65, 0.00, 0.00},
// Depuis iii (2)
{0.00, 0.00, 0, 0.25, 0.15, 0.50, 0.10},
// Depuis IV (3)
{0.45, 0.15, 0.00, 0, 0.25, 0.15, 0.00},
// Depuis V (4)
{0.70, 0.00, 0.00, 0.10, 0, 0.20, 0.00},
// Depuis vi (5)
{0.15, 0.15, 0.00, 0.45, 0.25, 0, 0.00},
// Depuis vii<69> (6)
{0.80, 0.00, 0.00, 0.00, 0.20, 0.00, 0.00}};
//matrice g<>n<EFBFBD>r<EFBFBD>e par Deepseek, a globalement fait une gaussienne en donnant une probabilit<69> plus <20>lev<65>e au notes proches de la note jou<6F>
markov_matrix_melody = {// A1 (0)
{0.05, 0.30, 0.25, 0.15, 0.10, 0.05, 0.03, 0.02, 0.02,
0.01, 0.01, 0.00, 0.00, 0.00, 0.00},
// B1 (1)
{0.20, 0.05, 0.30, 0.20, 0.10, 0.05, 0.03, 0.02, 0.02,
0.01, 0.01, 0.00, 0.00, 0.00, 0.00},
// C2 (2)
{0.10, 0.20, 0.05, 0.30, 0.15, 0.10, 0.05, 0.02, 0.02,
0.01, 0.00, 0.00, 0.00, 0.00, 0.00},
// D2 (3)
{0.05, 0.15, 0.20, 0.05, 0.30, 0.15, 0.05, 0.03, 0.02,
0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
// E2 (4)
{0.03, 0.07, 0.15, 0.20, 0.05, 0.25, 0.15, 0.07, 0.03,
0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
// F2 (5)
{0.02, 0.05, 0.10, 0.15, 0.20, 0.05, 0.25, 0.15, 0.05,
0.02, 0.01, 0.00, 0.00, 0.00, 0.00},
// G2 (6)
{0.01, 0.03, 0.07, 0.10, 0.15, 0.20, 0.05, 0.25, 0.10,
0.03, 0.01, 0.00, 0.00, 0.00, 0.00},
// A2 (7) - Centre tonal
{0.00, 0.02, 0.05, 0.10, 0.15, 0.20, 0.25, 0.05, 0.15,
0.05, 0.02, 0.01, 0.00, 0.00, 0.00},
// B2 (8)
{0.00, 0.00, 0.01, 0.03, 0.07, 0.15, 0.20, 0.25, 0.05,
0.20, 0.10, 0.05, 0.03, 0.01, 0.00},
// C3 (9)
{0.00, 0.00, 0.00, 0.02, 0.05, 0.10, 0.15, 0.20, 0.25,
0.05, 0.20, 0.15, 0.07, 0.03, 0.01},
// D3 (10)
{0.00, 0.00, 0.00, 0.00, 0.03, 0.07, 0.10, 0.15, 0.20,
0.25, 0.05, 0.25, 0.15, 0.07, 0.03},
// E3 (11)
{0.00, 0.00, 0.00, 0.00, 0.00, 0.02, 0.05, 0.10, 0.15,
0.20, 0.25, 0.05, 0.20, 0.15, 0.08},
// F3 (12)
{0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.03, 0.07, 0.10,
0.15, 0.20, 0.25, 0.05, 0.25, 0.15},
// G3 (13)
{0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.02, 0.05,
0.10, 0.15, 0.20, 0.25, 0.05, 0.28},
// A3 (14)
{0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.03,
0.07, 0.10, 0.15, 0.20, 0.25, 0.05}};
chordProgression.resize(4);
int chord = firstChord();
chordProgression[0] = chord;
for (int i = 1; i < 4; i += 1) {
chord = nextChord(chord);
chordProgression[i] = chord;
}
//Son jou<6F> en boucle avec un volume nul, permet <20> tout les channels de s'accorder sur le tempo et d'obtenir le temps global
ERRCHECK(system->createSound("media/percussions/drums1.wav", FMOD_DEFAULT, nullptr,
&metronome_Sound));
ERRCHECK(system->playSound(metronome_Sound, nullptr, true, &timer));
ERRCHECK(timer->setVolume(0));
ERRCHECK(timer->setPaused(false));
}
int AudioEmitter::firstChord() {
std::vector<int> possibleChords = {1, 5, 6};
return possibleChords[rand() % possibleChords.size()] - 1;
}
int AudioEmitter::firstNote() {
std::vector<int> possibleNotes = {4, 5, 6, 7, 8, 9, 10, 11};
return possibleNotes[rand() % possibleNotes.size()];
}
int sampleIndex(const std::vector<float> &probabilities) {
//Renvoie un indice al<61>atoire selon un tableau de probabilit<69>s, fonction g<>n<EFBFBD>r<EFBFBD>e par ChatGPT
// Cr<43>er un g<>n<EFBFBD>rateur al<61>atoire
static std::random_device rd;
static std::mt19937 gen(rd());
// Cr<43>er une distribution discr<63>te avec les probabilit<69>s donn<6E>es
std::discrete_distribution<> dist(probabilities.begin(), probabilities.end());
// Tirer un <20>chantillon
return dist(gen);
}
int randomWeightedChoice(const std::vector<int> &values,
const std::vector<double> &weights) {
//Renvoie une valeur al<61>atoire selon un tableau de poids, fonction g<>n<EFBFBD>r<EFBFBD>e par ChatGPT
if (values.size() != weights.size() || values.empty()) {
throw std::invalid_argument(
"Les tableaux doivent <20>tre de m<>me taille et non vides.");
}
// Calcul de la somme totale des poids
double totalWeight = std::accumulate(weights.begin(), weights.end(), 0.0);
if (totalWeight <= 0) {
throw std::invalid_argument("La somme des poids doit <20>tre positive.");
}
// G<>n<EFBFBD>rateur al<61>atoire
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, totalWeight);
double r = dis(gen);
double cumulative = 0.0;
for (size_t i = 0; i < values.size(); ++i) {
cumulative += weights[i];
if (r < cumulative) {
return values[i];
}
}
// Fallback - devrait normalement ne jamais <20>tre atteint
return values.back();
}
int AudioEmitter::nextChord(int currentChord) {
return sampleIndex(markov_matrix_chords[currentChord]);
}
int AudioEmitter::nextNote(int currentNote) {
return sampleIndex(markov_matrix_melody[currentNote]);
}
/**
* generate music
*
* @return vector of the notes generated. first is when the note will be played
* in seconds, second is the note
*/
std::vector<std::pair<float, int>> AudioEmitter::generateMusic() {
//G<>n<EFBFBD>re la musique al<61>atoirement, et renvoie l'ensemble des notes sous forme pairs {s,n}, o<> s est le temps en seconde o<> la note est jou<6F>, et n la hauteur de la note
//Pour g<>n<EFBFBD>rer la musique al<61>atoirement, on utilise une cha<68>ne de Markov. Sachant qu'on joue un note donn<6E>e, on a un ensemble de probabilit<69>s pour jouer la note suivante
//Pour savoir <20> quel rythme nous jouons les notes, on tire al<61>atoirement dans un ensemble de rythmes possibles, on tire des rythmes plus ou moins complexe en fonction de si on est au d<>but ou <20> la fin de la musique
std::vector<std::pair<float, int>> result;
if (current_beat >= nbr_melo_total) { //On g<>n<EFBFBD>re un nombre fini de m<>lodies, pour avoir une musique de 4min
return result;
}
result.reserve(16 * nbr_melo_max);
float beatDuration = tempo / 60.f; //temps en seconde d'un beat musicale
unsigned int sampleRate = 48000;
int variation = 0;
if (((current_beat / nbr_melo_max) % 4) < 2) { //On change la mani<6E>re dont sont jou<6F>s les accords toutes les 2*nbr_melo_max m<>lodies g<>n<EFBFBD>r<EFBFBD>es
variation = 0;
}
else {
variation = 1;
}
int nbrChords = 7;
//Pour jouer des sons, on alloue des channel, pour <20>viter de se retrouver <20> cours de channel, on en lib<69>re r<>guli<6C>rement
//Ce syst<73>me permet de g<>n<EFBFBD>rer une musique potentiellement ind<6E>finiment
int maxsize = 400;
if (activeChannels.size() > maxsize) {
for (int i = 0; i < maxsize / 2; i += 1) {
if (activeChannels[i]) {
activeChannels[i]->stop();
}
}
}
//On cr<63><72> une boucle de 4 accords, qu'on va r<>p<EFBFBD>ter quelques fois avant de la faire varier
//Les accords jou<6F>s sont aussi d<>cid<69>s via une cha<68>ne de Markov
chordProgression[0] = nextChord(chordProgression[3]);
for (int i = 1; i < 4; i += 1) {
chordProgression[i] = nextChord(chordProgression[i - 1]);
}
for (int i = current_beat; i < current_beat + nbr_melo_max; i += 1) {
// Chords
FMOD::Channel *channelChords = nullptr;
int index_chord = chordProgression[i % 4];
ERRCHECK(system->playSound(chords[index_chord + variation*nbrChords].get(), nullptr, true,
&channelChords));
unsigned long long delay =
(unsigned long long)(i * beatDuration * sampleRate);
ERRCHECK(channelChords->setDelay(delay, 0, true));
ERRCHECK(channelChords->setPaused(false));
activeChannels.push_back(channelChords);
// M<>lodie
if (i >= 1) { //Pour laisser une mesure avant que les notes soient g<>n<EFBFBD>r<EFBFBD>es
//On choisi un rythme au hasard, plus ou moins complexe en fonction de l'avancement dans la musique
int index_rythme = floor(((i - 1) * 1.f / nbr_melo_total) * (rythmes.size() - 1)) + ( rand() % nbr_melo_max ); //Les rythmes deviennent de plus en plus complexe, plus on avance dans le temps, plus le rythme est tir<69> de la fin du vecteur
index_rythme = (int)fmin(index_rythme, rythmes.size() - 1); //pour <20>viter un index hors du tableau
std::vector<float> rythme_melodie = rythmes[index_rythme];
for (float time : rythme_melodie) {
FMOD::Channel* channelNote = nullptr;
ERRCHECK(system->playSound(notes[index_note].get(), nullptr, true,
&channelNote));
float note_start = (i + time / 8.f) * beatDuration;
unsigned long long delayNote =
(unsigned long long)(note_start * sampleRate);
ERRCHECK(channelNote->setDelay(delayNote, 0, true));
ERRCHECK(channelNote->setPaused(false));
result.push_back(std::pair<float, int>(note_start, index_note));
index_note = nextNote(index_note);
activeChannels.push_back(channelNote);
}
}
}
current_beat += nbr_melo_max;
return result;
}
void AudioEmitter::audioUpdate() { system->update(); }
void AudioEmitter::audioEnd() {
//release les diff<66>rents <20>l<EFBFBD>ments avant la fin du programme
//FMOD a sa propre gestion m<>moire, on doit donc simplement lui signaler que nous n'avons plus besoin de ces <20>l<EFBFBD>ments
for (int i = 0; i < activeChannels.size(); i += 1) {
if (activeChannels[i]) {
activeChannels[i]->stop();
}
}
for (int i = 0; i < notes.size(); i += 1) {
notes[i].get()->release();
}
for (int i = 0; i < chords.size(); i += 1) {
chords[i].get()->release();
}
timer->stop();
metronome_Sound->release();
system->close();
system->release();
}
float AudioEmitter::getTimeTempo() const {
//Renvoie le temps <20>coul<75> depuis le d<>but de la musique, en beat musicaux
//Par exemple, renvoie 4 au bout d'une mesure
float beatDuration = tempo / 60.f / 8.f;
unsigned long long dspClock = 0;
ERRCHECK(timer->getDSPClock(&dspClock, nullptr));
return dspClock / 48000.f / beatDuration;
}
float AudioEmitter::getTime() const {
//Renvoie le temps <20>coul<75> depuis le d<>but de la musique, en secondes
unsigned long long dspClock = 0;
ERRCHECK(timer->getDSPClock(&dspClock, nullptr));
return dspClock / 48000.f;
}