#include "AudioEmitter.hpp" #include #include #include #include #include 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 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 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(rawChords[i])); } for (int i = 0; i < 7; i += 1) { chords.push_back(std::unique_ptr(rawChords2[i])); } std::vector 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(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éré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° (6) {0.80, 0.00, 0.00, 0.00, 0.20, 0.00, 0.00}}; //matrice générée par Deepseek, a globalement fait une gaussienne en donnant une probabilité plus élevée au notes proches de la note joué 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é en boucle avec un volume nul, permet à 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 possibleChords = {1, 5, 6}; return possibleChords[rand() % possibleChords.size()] - 1; } int AudioEmitter::firstNote() { std::vector possibleNotes = {4, 5, 6, 7, 8, 9, 10, 11}; return possibleNotes[rand() % possibleNotes.size()]; } int sampleIndex(const std::vector &probabilities) { //Renvoie un indice aléatoire selon un tableau de probabilités, fonction générée par ChatGPT // Créer un générateur aléatoire static std::random_device rd; static std::mt19937 gen(rd()); // Créer une distribution discrète avec les probabilités données std::discrete_distribution<> dist(probabilities.begin(), probabilities.end()); // Tirer un échantillon return dist(gen); } int randomWeightedChoice(const std::vector &values, const std::vector &weights) { //Renvoie une valeur aléatoire selon un tableau de poids, fonction générée par ChatGPT if (values.size() != weights.size() || values.empty()) { throw std::invalid_argument( "Les tableaux doivent ê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 être positive."); } // Générateur alé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 ê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> AudioEmitter::generateMusic() { //Génère la musique aléatoirement, et renvoie l'ensemble des notes sous forme pairs {s,n}, où s est le temps en seconde où la note est joué, et n la hauteur de la note //Pour générer la musique aléatoirement, on utilise une chaîne de Markov. Sachant qu'on joue un note donnée, on a un ensemble de probabilités pour jouer la note suivante //Pour savoir à quel rythme nous jouons les notes, on tire alé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 à la fin de la musique std::vector> result; if (current_beat >= nbr_melo_total) { //On génè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ère dont sont joués les accords toutes les 2*nbr_melo_max mélodies générées variation = 0; } else { variation = 1; } int nbrChords = 7; //Pour jouer des sons, on alloue des channel, pour éviter de se retrouver à cours de channel, on en libère régulièrement //Ce système permet de générer une musique potentiellement indé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éé une boucle de 4 accords, qu'on va répéter quelques fois avant de la faire varier //Les accords joués sont aussi décidés via une chaî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éré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é de la fin du vecteur index_rythme = (int)fmin(index_rythme, rythmes.size() - 1); //pour éviter un index hors du tableau std::vector 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(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érents élé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 élé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 écoulé 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 écoulé depuis le début de la musique, en secondes unsigned long long dspClock = 0; ERRCHECK(timer->getDSPClock(&dspClock, nullptr)); return dspClock / 48000.f; }