367 lines
14 KiB
C++
367 lines
14 KiB
C++
#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/accords/do.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[0]));
|
||
ERRCHECK(system->createSound("media/accords/re.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[1]));
|
||
ERRCHECK(system->createSound("media/accords/mi.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[2]));
|
||
ERRCHECK(system->createSound("media/accords/fa.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[3]));
|
||
ERRCHECK(system->createSound("media/accords/sol.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[4]));
|
||
ERRCHECK(system->createSound("media/accords/la.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[5]));
|
||
ERRCHECK(system->createSound("media/accords/si.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawChords[6]));
|
||
for (int i = 0; i < 7; i += 1) {
|
||
chords.push_back(std::unique_ptr<FMOD::Sound>(rawChords[i]));
|
||
}
|
||
|
||
int nbr_drums = 3;
|
||
std::vector<FMOD::Sound *> rawDrums(nbr_drums);
|
||
ERRCHECK(system->createSound("media/percussions/drums1.wav", FMOD_LOOP_OFF,
|
||
nullptr, &rawDrums[0]));
|
||
ERRCHECK(system->createSound("media/percussions/drums2.wav", FMOD_LOOP_OFF,
|
||
nullptr, &rawDrums[1]));
|
||
ERRCHECK(system->createSound("media/percussions/drums3.wav", FMOD_LOOP_OFF,
|
||
nullptr, &rawDrums[2]));
|
||
for (int i = 0; i < nbr_drums; i += 1) {
|
||
drums.push_back(std::unique_ptr<FMOD::Sound>(rawDrums[i]));
|
||
}
|
||
|
||
std::vector<FMOD::Sound *> rawNotes(15);
|
||
ERRCHECK(system->createSound("media/notes/A1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[0]));
|
||
ERRCHECK(system->createSound("media/notes/B1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[1]));
|
||
ERRCHECK(system->createSound("media/notes/C1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[2]));
|
||
ERRCHECK(system->createSound("media/notes/D1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[3]));
|
||
ERRCHECK(system->createSound("media/notes/E1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[4]));
|
||
ERRCHECK(system->createSound("media/notes/F1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[5]));
|
||
ERRCHECK(system->createSound("media/notes/G1.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[6]));
|
||
ERRCHECK(system->createSound("media/notes/A2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[7]));
|
||
ERRCHECK(system->createSound("media/notes/B2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[8]));
|
||
ERRCHECK(system->createSound("media/notes/C2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[9]));
|
||
ERRCHECK(system->createSound("media/notes/D2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[10]));
|
||
ERRCHECK(system->createSound("media/notes/E2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[11]));
|
||
ERRCHECK(system->createSound("media/notes/F2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[12]));
|
||
ERRCHECK(system->createSound("media/notes/G2.wav", FMOD_LOOP_OFF, nullptr,
|
||
&rawNotes[13]));
|
||
ERRCHECK(system->createSound("media/notes/A3.wav", 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, 2, 4, 6},
|
||
{0, 1, 2, 3, 4, 5, 6, 7},
|
||
{0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7},
|
||
{0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5},
|
||
{0, 0.5, 1, 2, 2.5, 3, 4, 4.5, 5, 6, 6.5, 7},
|
||
{0.5, 1, 1.5, 2, 3.5, 4, 4.5, 5, 6.5, 7, 7.5},
|
||
{0, 1, 2.5, 3, 4, 5, 5.5, 6.5, 7, 7.5},
|
||
{0, 0.75, 1.5, 2.5, 3, 3.5, 5, 5.5},
|
||
{0, 0.5, 1, 1.5, 2, 3, 4, 4.5, 5, 6, 6.5, 7, 7.5},
|
||
{0, 0.5, 1, 1.5, 2.5, 3.5, 4, 5, 6, 7},
|
||
{0, 1.5, 3, 4, 5.5, 7},
|
||
{0, 0.5, 1.5, 2, 3, 4, 4.5, 5.5, 6, 7},
|
||
{0.5, 1, 1.5, 2, 2.75, 3.5, 4.5, 5, 5.5, 6, 6.5, 7, 7.5},
|
||
{0, 0.75, 1.5, 2, 2.75, 3.5, 4, 4.5, 5, 5.5, 6, 7, 7.5},
|
||
{0, 0.5, 1.5, 2, 3, 3.5, 4.5, 5, 6, 6.5, 7.5},
|
||
{0, 1, 2, 2.75, 3.5, 4.5, 5, 5.5, 6, 6.75, 7.5},
|
||
{0.5, 1, 1.5, 2, 2.5, 3.5, 4.5, 5, 5.5, 6, 6.5, 7},
|
||
{0, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5.5, 7},
|
||
{0, 1.5, 2.5, 3, 4, 5.5, 6.5, 7},
|
||
{0, 1.5, 3, 3.5, 4, 5.5, 7},
|
||
{0, 0.5, 1.5, 2, 2.5, 3.5, 4, 4.5, 5, 5.5},
|
||
{0, 0.5, 1, 2, 2.5, 3, 4, 4.5, 5, 6, 6.5, 7},
|
||
{0, 1.5, 3, 4.5, 6, 7},
|
||
{0, 0.5, 1, 1.5, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6.5, 7, 7.5},
|
||
{1, 2, 3.5, 4},
|
||
{0, 1.5, 3, 4, 5.5, 7},
|
||
{0, 2, 2.5, 3, 3.5, 4.5, 5.5, 6.5, 7},
|
||
{0.5, 1, 1.5, 2, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7},
|
||
{0, 0.5, 1, 1.5, 3, 4, 4.5, 5, 5.5, 7},
|
||
};
|
||
|
||
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}};
|
||
|
||
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;
|
||
}
|
||
|
||
FMOD::Sound *metronome_Sound;
|
||
ERRCHECK(system->createSound("media/notes/A1.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) {
|
||
// 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) {
|
||
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]);
|
||
}
|
||
|
||
int AudioEmitter::noteSecondaire(int note) {
|
||
std::vector<int> notesPossibles = {note - 4, note - 3, note + 3, note + 4,
|
||
note + 5};
|
||
std::vector<double> proba = {0.05, 0.10, 0.60, 0.05, 0.2};
|
||
for (int i = 0; i < proba.size(); i += 1) {
|
||
if ((notesPossibles[i] < 0) || (notesPossibles[i] >= notes.size())) {
|
||
proba[i] = 0;
|
||
}
|
||
}
|
||
return randomWeightedChoice(notesPossibles, proba);
|
||
}
|
||
|
||
/**
|
||
* 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() {
|
||
std::vector<std::pair<float, int>> result;
|
||
result.reserve(16 * nbr_melo_max);
|
||
float beatDuration = tempo / 60.f;
|
||
unsigned int sampleRate = 48000;
|
||
int maxsize = 400;
|
||
if (activeChannels.size() > maxsize) {
|
||
for (int i = 0; i < maxsize / 2; i += 1) {
|
||
if (activeChannels[i]) {
|
||
activeChannels[i]->stop();
|
||
}
|
||
}
|
||
}
|
||
chordProgression[0] = nextChord(chordProgression[3]);
|
||
for (int i = 1; i < 4; i += 1) {
|
||
chordProgression[i] = nextChord(chordProgression[i - 1]);
|
||
}
|
||
int index_drums = rand() % 3;
|
||
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].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);
|
||
// Drums
|
||
FMOD::Channel *channelDrums = nullptr;
|
||
ERRCHECK(system->playSound(drums[index_drums].get(), nullptr, true,
|
||
&channelDrums));
|
||
ERRCHECK(channelDrums->setDelay(delay, 0, true));
|
||
ERRCHECK(channelDrums->setPaused(false));
|
||
|
||
activeChannels.push_back(channelDrums);
|
||
// M<>lodie
|
||
std::vector<float> rythme_melodie = rythmes[rand() % rythmes.size()];
|
||
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;
|
||
printf("note start %f \n", note_start);
|
||
unsigned long long delayNote =
|
||
(unsigned long long)(note_start * sampleRate);
|
||
ERRCHECK(channelNote->setDelay(delayNote, 0, true));
|
||
ERRCHECK(channelNote->setPaused(false));
|
||
index_note = nextNote(index_note);
|
||
result.push_back(std::pair<float, int>(note_start, index_note));
|
||
activeChannels.push_back(channelNote);
|
||
}
|
||
}
|
||
current_beat += nbr_melo_max;
|
||
return result;
|
||
}
|
||
|
||
void AudioEmitter::audioUpdate() { system->update(); }
|
||
|
||
void AudioEmitter::audioEnd() {
|
||
for (FMOD::Channel *c : activeChannels) {
|
||
delete c;
|
||
}
|
||
timer->stop();
|
||
system->close();
|
||
system->release();
|
||
}
|
||
|
||
float AudioEmitter::getTimeTempo() const {
|
||
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 {
|
||
unsigned long long dspClock = 0;
|
||
ERRCHECK(timer->getDSPClock(&dspClock, nullptr));
|
||
return dspClock / 48000.f;
|
||
}
|