/**************************************************************************
Copyright (C) 2019-2022 Arnaud Champenois arthelion@free.fr
This program is free software: you can redistribute it and/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/>.
**************************************************************************/
#include "SynthVoice.h"
#include "PluginProcessor.h"
#include <iostream>
bool SynthVoice::mVoiceInitialized = false;
SynthVoice::SynthVoice(double sampleRate, int /*samplePerBlock*/,
MolossI& proc) :
mP(*proc.getSynthParameters())
{
mIsOn = false;
mBalanceLFO = std::make_unique<LFOModulated>(*proc.getSynthParameters());
mLFOArray.push_back(mBalanceLFO.get());
mCtrlArray.push_back(mBalanceLFO.get());
for (int i = 0; i < VCO_NUMBER; ++i) {
mPitchArray.push_back(std::unique_ptr<Pitch>(new Pitch(*proc.getPitchParameters(i))));
mPitchArray[i]->init(float(sampleRate));
mLFOArray.push_back(mPitchArray[i].get());
mTVArray.push_back(mPitchArray[i].get());
mCtrlArray.push_back(mPitchArray[i].get());
mVCOArray.push_back(std::unique_ptr<VCO>(new VCO(*proc.getVCOParameters(i))));
mVCOArray[i]->init(float(sampleRate));
mLFOArray.push_back(mVCOArray[i].get());
mCtrlArray.push_back(mVCOArray[i].get());
mTVFArray.push_back(std::unique_ptr<TVF>(new TVF(*proc.getTVFParameters(i))));
mTVFArray[i]->init(float(sampleRate));
mLFOArray.push_back(mTVFArray[i].get());
mTVArray.push_back(mTVFArray[i].get());
mCtrlArray.push_back(mTVFArray[i].get());
mTVAArray.push_back(std::unique_ptr<TVA>(new TVA(*proc.getTVAParameters(i))));
mTVAArray[i]->init(float(sampleRate));
mLFOArray.push_back(mTVAArray[i].get());
mTVArray.push_back(mTVAArray[i].get());
mCtrlArray.push_back(mTVAArray[i].get());
if (i < (VCO_NUMBER - 1)) {
mVCOFMDepth[i] = 0.0;
mVCORingDepth[i] = 0.0;
}
mVCOIsActive[i] = false;
mVCODepthL[i] = 0.0;
mVCODepthR[i] = 0.0;
mVCOBalLFODepth[i] = 0.0;
}
juce::Random random;
if(!mVoiceInitialized) {
mVoiceInitialized = true;
}
}
SynthVoice::~SynthVoice()
{
}
bool SynthVoice::canPlaySound(SynthesiserSound *)
{
return true;
}
bool SynthVoice::isPlayingChannel(const int /*midiChannel*/) const
{
return true; // The voices are always concerned by every event
}
void SynthVoice::startNote(int midiNoteNumber, float velocity, SynthesiserSound * /*sound*/, int /*currentPitchWheelPosition*/)
{
const ScopedLock lock(voiceLock);
mIsOn = true;
mExtinctionRamp = 0.0;
//Pitch weel is tracked by each voice, no need to restore it here
for (ControllerResponsive*ctrl : mCtrlArray) {
ctrl->noteOn(midiNoteNumber);
ctrl->setVelocity(velocity);
}
}
void SynthVoice::changeNote(int midiNoteNumber)
{
const ScopedLock lock(voiceLock);
// Legato mode
for (int i = 0; i < mPitchArray.size(); i++) {
mPitchArray[i]->changeNote(midiNoteNumber);
}
setCurrentPlayingNote(midiNoteNumber);
}
void SynthVoice::stopNote(float velocity, bool allowTailOff)
{
const ScopedLock lock(voiceLock);
if (mIsOn) {
mIsOn = false;
for (ControllerResponsive*ctrl : mCtrlArray) {
ctrl->noteOff(velocity);
}
}
if (!allowTailOff)
clearCurrentNote();
}
void SynthVoice::pitchWheelMoved(int newPitchWheelValue)
{
const ScopedLock lock(voiceLock);
for (ControllerResponsive*ctrl : mCtrlArray)
ctrl->setPitchWheelValue(newPitchWheelValue);
}
void SynthVoice::controllerMoved(int controllerNumber, int newControllerValue)
{
const ScopedLock lock(voiceLock);
switch (controllerNumber) {
case ModWheelCtrl: // Modulation Wheel
for (ControllerResponsive*ctrl : mCtrlArray)
ctrl->setModWheelValue(newControllerValue);
break;
default:
for (ControllerResponsive*ctrl : mCtrlArray)
ctrl->setControllerValue(controllerNumber, newControllerValue);
break;
}
}
void SynthVoice::aftertouchChanged(int newAftertouchValue)
{
const ScopedLock lock(voiceLock);
for (ControllerResponsive*ctrl : mCtrlArray)
ctrl->setAfterTouchValue(newAftertouchValue);
}
void SynthVoice::channelPressureChanged(int newAftertouchValue)
{
aftertouchChanged(newAftertouchValue);
}
void SynthVoice::renderNextBlock(AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
{
const ScopedLock lock(voiceLock);
// Si inactif on avance simplement les générateurs de LFO en phase
if (!isVoiceActive()) {
mSkippedSamples+= unsigned(numSamples);
if (mSkippedSamples > (1 << 30)) {
moveLFOToPhase();
}
return;
}
if (mSkippedSamples) {
moveLFOToPhase();
}
double voiceOutput[VCO_NUMBER];
double voiceTVF[VCO_NUMBER];
double voiceVCO[VCO_NUMBER];
bool tvaISActive = false;
lockVoices();
while (--numSamples >= 0) {
double voiceMixL = 0.0;
double voiceMixR = 0.0;
// Out of the voice loop because we use the value for all voices.
// If all voices are inactive, it still keeps the LFO in phase.
double lfoBalance = mBalanceLFO->nextCenteredLFO();
for (int v = 0; v < VCO_NUMBER; v++) {
// NOt active => juste keep LFO in phase
if (!mVCOIsActive[v]) {
moveLFO(v);
continue;
}
// Not in the span => just keep the LFO in phase
if (mPitchArray[v]->getNote() < mVCOMinNote[v] ||
mPitchArray[v]->getNote() > mVCOMaxNote[v]) {
// Voice still could be used by others => put the output to 0
voiceOutput[v] = voiceTVF[v] = voiceVCO[v] = 0.0;
moveLFO(v);
continue;
}
// Compute note frequency
double freq = mPitchArray[v]->next();
if (v > 0 && mVCOIsActive[v - 1]) {
// Frequency modulation from previous voice
freq = freq + mVCOFMDepth[v - 1] * voiceOutput[v - 1];
}
if (freq >= getSampleRate()*0.5) {
voiceOutput[v] = voiceTVF[v] = voiceVCO[v] = 0.0;
moveLFO(v);
continue;
}
// Get sound from frequency
voiceVCO[v] = mVCOArray[v]->next(freq);
// Filter the sound
voiceTVF[v] = mTVFArray[v]->next(voiceVCO[v]);
// Ring modulation level
if (v > 0 && mVCOIsActive[v - 1])
voiceTVF[v] = (1.0 - mVCORingDepth[v - 1])*voiceTVF[v] +
mVCORingDepth[v - 1] * voiceTVF[v] * voiceTVF[v - 1];
voiceOutput[v] = mTVAArray[v]->next(voiceTVF[v]);
if (mBalanceLFO->isLFOBypassed() || mVCOBalLFODepth[v] == 0.0) {
// No balance LFO
voiceMixR += mVCODepthR[v] * voiceOutput[v];
voiceMixL += mVCODepthL[v] * voiceOutput[v];
}
else {
// Balance LFO
double coeff1 = mVCODepthR[v] * (1.0 + mVCOBalLFODepth[v] * lfoBalance);
double coeff2 = mVCODepthL[v] * (1.0 - mVCOBalLFODepth[v] * lfoBalance);
if (coeff1 < 0) coeff1 = 0.0;
else if (coeff1 > 1.0) coeff1 = 1.0;
if (coeff2 < 0) coeff2 = 0.0;
else if (coeff2 > 1.0) coeff2 = 1.0;
voiceMixR += coeff1 * voiceOutput[v];
voiceMixL += coeff2 * voiceOutput[v];
}
tvaISActive = tvaISActive || mTVAArray[v]->isActive();
}
// We avoid to cut sounds abruptly, we use a small ramp
if (mExtinctionRamp > 0.0) {
if (mExtinctionFader > 0.0) {
mExtinctionFader -= mExtinctionRamp;
if (mExtinctionFader < 0.0)
mExtinctionFader = 0.0;
}
voiceMixL *= mExtinctionFader;
voiceMixR *= mExtinctionFader;
}
outputBuffer.addSample(0, startSample, float(voiceMixL));
outputBuffer.addSample(1, startSample, float(voiceMixR));
++startSample;
}
unlockVoices();
if (!tvaISActive || (mExtinctionRamp > 0.0 && mExtinctionFader == 0.0)) {
// At the end of ADSR or fade-out, the voice is finished
mExtinctionRamp = 0.0;
mIsOn = false; // whenever it received a note off or not.
clearCurrentNote();
}
}
void SynthVoice::unlockVoices()
{
//for (int v = 0; v < VCO_NUMBER; v++) {
// mVCOArray[v]->getLock().exit();
// mPitchArray[v]->getLock().exit();
// mTVAArray[v]->getLock().exit();
// mTVFArray[v]->getLock().exit();
//}
}
void SynthVoice::lockVoices()
{
//for (int v = 0; v < VCO_NUMBER; v++) {
// mVCOArray[v]->getLock().enter();
// mPitchArray[v]->getLock().enter();
// mTVAArray[v]->getLock().enter();
// mTVFArray[v]->getLock().enter();
//}
}
void SynthVoice::moveLFOToPhase()
{
const ScopedLock lock(voiceLock);
float samples = float(mSkippedSamples);
for (LFOModulated*lfo : mLFOArray) {
lfo->moveLFO(samples);
}
mSkippedSamples = 0;
}
void SynthVoice::moveLFO(int v)
{
const ScopedLock lock(voiceLock);
mPitchArray[v]->moveLFO(1);
mVCOArray[v]->moveLFO(1);
mTVFArray[v]->moveLFO(1);
mTVAArray[v]->moveLFO(1);
}
void SynthVoice::forceExtinction()
{
const ScopedLock lock(voiceLock);
// A fade-out is running
if (mExtinctionRamp != 0.0) return;
// Launch a fade-out
mExtinctionRamp = 1.0 / (2.0*ADSRMinAttackRelease * float(getSampleRate()));
mExtinctionFader = 1.0;
}
void SynthVoice::resetLFOPhase()
{
const ScopedLock lock(voiceLock);
for (LFOModulated*lfo : mLFOArray) {
lfo->resetPhase();
}
}
void SynthVoice::updateLFO(int lfoNum)
{
const ScopedLock lock(voiceLock);
mLFOArray[lfoNum]->updateInternalValues();
resetLFOPhase(); // To have everyone in line
}
void SynthVoice::updateADSR(int adsrNum)
{
const ScopedLock lock(voiceLock);
mTVArray[adsrNum]->updateInternalValues();
}
void SynthVoice::updateVCO(int adsrNum)
{
const ScopedLock lock(voiceLock);
mVCOArray[adsrNum]->updateInternalValues();
}
void SynthVoice::updateTVF(int tvfNum)
{
const ScopedLock lock(voiceLock);
mTVFArray[tvfNum]->updateInternalValues();
}
void SynthVoice::updateTVA(int tvaNum)
{
const ScopedLock lock(voiceLock);
mTVAArray[tvaNum]->updateInternalValues();
}
void SynthVoice::updatePitch(int pitchNum)
{
const ScopedLock lock(voiceLock);
mPitchArray[pitchNum]->updateInternalValues();
}
void SynthVoice::updateInternalValues()
{
const ScopedLock lock(voiceLock);
for (int i = 0; i < VCO_NUMBER; i++) {
double balance = float(*mP.mVCOBalance[i]);
if (bool(*mP.mVCOActivate[i])) {
mVCODepthL[i] = float(*mP.mVCODepth[i])*(balance > 0.0 ? (1.0 - balance) : 1.0);
mVCODepthR[i] = float(*mP.mVCODepth[i])*(balance < 0.0 ? (1.0 + balance) : 1.0);
}
else {
mVCODepthL[i] = mVCODepthR[i] = 0.0;
}
mVCOMinNote[i] = int(*mP.mVCOMinNoteNumber[i]);
mVCOMaxNote[i] = int(*mP.mVCOMaxNoteNumber[i]);
if (i < VCO_NUMBER - 1) {
mVCOFMDepth[i] = float(*mP.mVCOFMDepth[i]);
mVCORingDepth[i] = float(*mP.mVCORingDepth[i]);
}
mVCOIsActive[i] = bool(*mP.mVCOIsActive[i]);
mVCOBalLFODepth[i] = float(*mP.mVCOBalLFODepth[i]);
}
}