247 lines
7.7 KiB
C++
247 lines
7.7 KiB
C++
/*
|
|
==============================================================================
|
|
Copyright 2022 Nicolas Chambert
|
|
|
|
This program is free software : you can redistribute itand /or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program.If not, see < http://www.gnu.org/licenses/>.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
Moteur de l'application. Gère la lecture des samples Callé sur le bon tempo.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include "Metronome.h"
|
|
|
|
Metronome::Metronome(Config& conf)
|
|
{
|
|
// on récupère les valeurs de la configuration
|
|
bpm.set(conf.bpm);
|
|
measures.set(conf.measures);
|
|
gain.set(conf.gain);
|
|
setFigure(conf.figure);
|
|
|
|
// on initialise le lecteur wav
|
|
formatManager.registerBasicFormats();
|
|
|
|
// On crée 2 voie sur le synthé
|
|
for (auto i = 0; i < 2; ++i)
|
|
synth.addVoice(new juce::SamplerVoice());
|
|
}
|
|
|
|
Metronome::~Metronome()
|
|
{
|
|
// libère les sons
|
|
synth.clearSounds();
|
|
}
|
|
|
|
|
|
void Metronome::loadSounds(std::string high, std::string low)
|
|
{
|
|
// chargement des sons high et low
|
|
auto mySamplesHigh = juce::File{high};
|
|
auto mySamplesLow = juce::File{ low };
|
|
|
|
jassert(mySamplesHigh.exists());
|
|
jassert(mySamplesLow.exists());
|
|
synth.clearSounds();
|
|
|
|
juce::BigInteger usedNotes;
|
|
usedNotes.setRange(0, 1, true);
|
|
auto formatReaderHigh = formatManager.createReaderFor(mySamplesHigh);
|
|
synth.addSound(new juce::SamplerSound("High", *formatReaderHigh, usedNotes, 0, 0.0f, 0.0f, 999.0));
|
|
delete formatReaderHigh;
|
|
|
|
juce::BigInteger usedNotes2;
|
|
usedNotes2.setRange(1, 1, true);
|
|
auto formatReaderLow = formatManager.createReaderFor(mySamplesLow);
|
|
synth.addSound(new juce::SamplerSound("Low", *formatReaderLow, usedNotes2, 1, 0.0f, 0.0f, 999.0));
|
|
delete formatReaderLow;
|
|
}
|
|
|
|
void Metronome::prepareToPlay (double sr)
|
|
{
|
|
// Appelé avant de démaré la lecture, on récupère la sr et on initialise le reste du moteur
|
|
sampleRate = sr;
|
|
updateInterval = (int)(60.0 / bpm.get() * sampleRate);
|
|
synth.setCurrentPlaybackSampleRate(sampleRate);
|
|
initMeasure();
|
|
}
|
|
|
|
void Metronome::getNextAudioBlock(juce::AudioSourceChannelInfo const& bufferToFill)
|
|
{
|
|
// Méthode invoqué par le moteur audio. Le but est de remplir bufferToFill avec la waveform que l'on souhaite jouer
|
|
int const numSamples = bufferToFill.numSamples;
|
|
|
|
juce::MidiBuffer midi;
|
|
int measCount = measures.get();
|
|
|
|
int measLength = updateInterval * measCount; // nombre de sample total dans une mesure
|
|
int start = lastPos; // Le premier index du buffer correspond au lastPos
|
|
int stop = lastPos + numSamples; // et la dernière position du buffer
|
|
for (auto cur : measStruct)
|
|
{
|
|
// On cherche Si dans notre mesure on a un click entre start et stop
|
|
int nextTick = (int)(cur.measPos * updateInterval); // le measPos est en relatif, on convertit en équivalent samples
|
|
if (stop > measLength)
|
|
{
|
|
// Dans le cas ou stop se situe sur la mesure suivante, on décale le nextTick d'une mesure
|
|
nextTick += measLength;
|
|
}
|
|
|
|
if (nextTick >= start && nextTick < stop)
|
|
{
|
|
// On à trouvé un tick
|
|
if (cur.type != NoteType::mute)
|
|
{
|
|
// qui est pas mute
|
|
// on sélectionne la bone note, la bonne vélocité et on génère un événement midi à nextTick - start
|
|
int note = cur.type == NoteType::high ? 0 : 1;
|
|
float vel = cur.type == NoteType::lowLow ? 0.2f : 1.0f;
|
|
midi.addEvent(juce::MidiMessage::noteOn(1, note, vel), nextTick - start);
|
|
}
|
|
|
|
// on signale l'interface (pour marquer le temps même s'il est mute)
|
|
signal.set(true);
|
|
}
|
|
}
|
|
|
|
// On incrémente lastPos
|
|
lastPos = stop;
|
|
if (lastPos > measLength)
|
|
{
|
|
// Et on le remet dans la mesure suivante
|
|
lastPos -= measLength;
|
|
}
|
|
|
|
// On demande au synthé de remplir le buffer
|
|
synth.renderNextBlock(*bufferToFill.buffer,
|
|
midi,
|
|
0,
|
|
bufferToFill.numSamples);
|
|
|
|
// On reprend la main sur le buffer pour appliquer le gain manuellement
|
|
float mult = gain.get();
|
|
for (auto channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
|
|
{
|
|
auto* buffer = bufferToFill.buffer->getWritePointer(channel, bufferToFill.startSample);
|
|
|
|
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
|
|
buffer[sample] = buffer[sample] * mult;
|
|
}
|
|
}
|
|
|
|
void Metronome::reset()
|
|
{
|
|
// raz
|
|
lastPos = 0;
|
|
signal.set(true);
|
|
}
|
|
|
|
void Metronome::setBPM(int value)
|
|
{
|
|
// contraint les BPM entre 10 et 400
|
|
int coerce = juce::jlimit(10, 400, value);
|
|
bpm.set(coerce);
|
|
|
|
int lastInterval = updateInterval;
|
|
updateInterval = (int)(60.0 / bpm.get() * sampleRate);
|
|
// En cas de changement de BPM brutal on constate un décalage dans la lecture
|
|
// Pour remédier à ca on recalibre le lastPos en proportion du nouvel intervale
|
|
lastPos = (int)(lastPos * (float)updateInterval / (float)lastInterval);
|
|
}
|
|
|
|
int Metronome::getCurrentMeasure() const
|
|
{
|
|
// permet à l'UI de savoir sur quel temps de la mesure on se trouve
|
|
int numTemps = (lastPos + updateInterval - 1) / updateInterval; // nombre de temps depuis le start
|
|
return numTemps;
|
|
}
|
|
|
|
void Metronome::initMeasure()
|
|
{
|
|
// construit la structure de la mesure
|
|
measStruct.clear();
|
|
int measCount = measures.get();
|
|
switch (figure.get())
|
|
{
|
|
case FigureType::blanche:
|
|
// On alterne click/Mute
|
|
for (size_t i = 0; i < measCount; ++i)
|
|
{
|
|
if (i % 2 == 0)
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
|
|
}
|
|
else
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, NoteType::mute));
|
|
}
|
|
}
|
|
|
|
break;
|
|
case FigureType::noire:
|
|
// Un click par temps
|
|
for (size_t i = 0; i < measCount; ++i)
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
|
|
}
|
|
|
|
break;
|
|
case FigureType::croche:
|
|
// On alterne click/lowlow
|
|
for (size_t i = 0; i < measCount; ++i)
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
|
|
measStruct.push_back(MeasureTick((float)i + 0.5f, NoteType::lowLow));
|
|
}
|
|
|
|
break;
|
|
case FigureType::triolet:
|
|
// On alterne click/lowlow/lowlow
|
|
for (size_t i = 0; i < measCount; ++i)
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
|
|
measStruct.push_back(MeasureTick((float)i + 1.f / 3.f, NoteType::lowLow));
|
|
measStruct.push_back(MeasureTick((float)i + 2.f / 3.f, NoteType::lowLow));
|
|
}
|
|
break;
|
|
case FigureType::doubleCroche:
|
|
// On alterne click/lowlow/lowlow/lowlow
|
|
for (size_t i = 0; i < measCount; ++i)
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
|
|
measStruct.push_back(MeasureTick((float)i + 0.25f, NoteType::lowLow));
|
|
measStruct.push_back(MeasureTick((float)i + 0.5f, NoteType::lowLow));
|
|
measStruct.push_back(MeasureTick((float)i + 0.75f, NoteType::lowLow));
|
|
}
|
|
break;
|
|
case FigureType::swing:
|
|
// On alterne click/lowlow sur les 2/3 du temps
|
|
for (size_t i = 0; i < measCount; ++i)
|
|
{
|
|
measStruct.push_back(MeasureTick((float)i, i == 0 ? NoteType::high : NoteType::low));
|
|
measStruct.push_back(MeasureTick((float)i + 2.f / 3.f, NoteType::lowLow));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|