Magical8bitPlug2/Source/TonalVoice.cpp
2021-08-16 14:35:28 +09:00

316 lines
9.8 KiB
C++

/*
==============================================================================
TonalVoice.cpp
Created: 11 Nov 2019 9:36:34pm
Author: 除村 武志
==============================================================================
*/
#include "TonalVoice.h"
//---------------------------------------------
//
// Tonal Voice
// The base for pulse/triangle (Abstract)
//
//---------------------------------------------
TonalVoice::TonalVoice (SettingRefs* sRefs) : BaseVoice (sRefs) {}
void TonalVoice::startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int currentPitchBendPosition)
{
BaseVoice::startNote (midiNoteNumber, velocity, 0, currentPitchBendPosition);
currentBendAmount = * (settingRefs->bendRange) * ((double) (currentPitchBendPosition - 8192)) / 8192.0;
currentPitchSequenceFrame = 0;
vibratoCount = 0;
float iniPitch = * (settingRefs->sweepInitialPitch);
float time = * (settingRefs->sweepTime);
currentAutoBendAmount = iniPitch;
autoBendDelta = -1.0 * iniPitch / (time * getSampleRate());
portamentoTime = 0;
currentArpeggioFrame = 0;
arpeggioFrameTimer = 0;
arpeggioFrameLength = 0;
currentNumNoteBuffer = 0;
for (int i=0; i<NUMNOTEBUFFER; i++) { noteBuffer[i] = 0; }
currentNumRetireBuffer = 0;
for (int i=0; i<NUMNOTEBUFFER; i++) { retireBuffer[i] = 0; }
}
void TonalVoice::advanceControlFrame()
{
BaseVoice::advanceControlFrame();
currentPitchSequenceFrame = settingRefs->pitchSequence.nextIndexOf (currentPitchSequenceFrame);
}
void TonalVoice::calculateAngleDelta()
{
int noteNumberMod = 0;
double finePitchInSeq = 0;
if (settingRefs->isPitchSequenceEnabled())
{
switch (settingRefs->pitchSequenceMode())
{
case kPitchSequenceModeFine:
finePitchInSeq = (double)settingRefs->pitchSequence.valueAt (currentPitchSequenceFrame) / 8.0;
break;
case kPitchSequenceModeCoarse:
noteNumberMod = settingRefs->pitchSequence.valueAt (currentPitchSequenceFrame);
break;
default:
break;
}
}
double byWheel = settingRefs->vibratoIgnoresWheel() ? 1.0 : currentModWheelValue;
double vibratoAmount = * (settingRefs->vibratoDepth) * sin (getVibratoPhase()) * byWheel;
double noteNoInDouble = noteNumber
+ noteNumberMod
+ currentBendAmount
+ currentAutoBendAmount
+ vibratoAmount
+ finePitchInSeq;
auto cyclesPerSecond = noteNoToHeltzDouble (noteNoInDouble);
auto cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
}
void TonalVoice::pitchWheelMoved (int amount)
{
currentBendAmount = * (settingRefs->bendRange) * ((double) (amount - 8192)) / 8192.0;
}
void TonalVoice::controllerMoved (int type, int amount)
{
if (type == 1) // Modulation
{
currentModWheelValue = (double)amount / 127.0;
}
}
void TonalVoice::setLegatoMode(double time, int midiCh) {
portamentoTime = time;
primaryMidiChannel = midiCh;
}
// The interface says "add" but the implementation is just using the latest value.
// It is because the original intension was to keep all the pressing keys and choose the apropriate one with certain algorithm
void TonalVoice::addLegatoNote (int midiNoteNumber, float velocity) {
if (currentNumNoteBuffer >= NUMNOTEBUFFER) {
return;
}
int previousNoteNo = noteNumber;
BaseVoice::changeNote(midiNoteNumber, velocity);
// Portamento implementation is just applying auto bend
if (portamentoTime > 0) {
currentAutoBendAmount = (double)(previousNoteNo - midiNoteNumber);
autoBendDelta = -1.0 * currentAutoBendAmount / (portamentoTime * getSampleRate());
}
}
int TonalVoice::removeLegatoNote(int midiNoteNumber) {
if (midiNoteNumber == noteNumber) {
return 0;
} else {
return 1;
}
}
void TonalVoice::setArpeggioMode(double interval, int midiCh)
{
arpeggioFrameLength = interval;
arpeggioFrameTimer = 0;
currentArpeggioFrame = 0;
currentNumNoteBuffer = 1;
noteBuffer[0] = noteNumber;
primaryMidiChannel = midiCh;
}
void TonalVoice::addArpeggioNoteAscending(int midiNoteNumber)
{
if (currentNumNoteBuffer >= NUMNOTEBUFFER) {
return;
}
int i;
for (i = 0; i<currentNumNoteBuffer; i++) {
if (noteBuffer[i] > midiNoteNumber) break;
}
pushNoteBuffer(i, midiNoteNumber);
currentNumNoteBuffer++;
}
void TonalVoice::addArpeggioNoteDescending(int midiNoteNumber)
{
if (currentNumNoteBuffer >= NUMNOTEBUFFER) {
return;
}
int i;
for (i = 0; i<currentNumNoteBuffer; i++) {
if (noteBuffer[i] < midiNoteNumber) break;
}
pushNoteBuffer(i, midiNoteNumber);
currentNumNoteBuffer++;
}
/// Returns number of remaining arpeggio notes
int TonalVoice::removeArpeggioNote(int midiNoteNumber)
{
if (currentNumNoteBuffer==0) {
return 0;
}
// Just push the note number to Retire Buffer
// Actual retirement happens in advanceFrameBuffer
retireBuffer[currentNumRetireBuffer] = midiNoteNumber;
currentNumRetireBuffer++;
// Observed like current number of notes already decreased
// so the Synthsizer can determine if it should enter Release phase
return currentNumNoteBuffer - currentNumRetireBuffer;
}
void TonalVoice::pushNoteBuffer(int index, int value) {
for (int i=NUMNOTEBUFFER-1; i>index; i--) {
noteBuffer[i] = noteBuffer[i-1];
}
noteBuffer[index] = value;
}
void TonalVoice::shiftNoteBuffer(int index) {
for (int i=index; i<NUMNOTEBUFFER-1; i++) {
noteBuffer[i] = noteBuffer[i+1];
}
}
double TonalVoice::noteNoToHeltzDouble (double noteNoInDouble, const double frequencyOfA)
{
return frequencyOfA * std::pow (2.0, (noteNoInDouble - 69) / 12.0);
}
double TonalVoice::getVibratoPhase()
{
double sec = (double)vibratoCount / getSampleRate();
float delay = * (settingRefs->vibratoDelay);
float rate = * (settingRefs->vibratoRate);
if ( sec < delay ) { return 0.0; }
double phase = fmodf (( sec - delay ), rate) / rate * MathConstants<double>::twoPi;
return phase;
}
void TonalVoice::onFrameAdvanced()
{
vibratoCount++;
currentAutoBendAmount = currentAutoBendAmount + autoBendDelta;
if (autoBendDelta > 0)
{
// positive slope
if (currentAutoBendAmount > 0)
{
currentAutoBendAmount = 0;
autoBendDelta = 0;
}
}
else
{
// negative slope
if (currentAutoBendAmount < 0)
{
currentAutoBendAmount = 0;
autoBendDelta = 0;
}
}
if (arpeggioFrameLength > 0) { // Arpeggio mode is on
arpeggioFrameTimer += 1.0 / getSampleRate();
if (arpeggioFrameTimer >= arpeggioFrameLength)
{ // Arpeggio phase advances
if (!isInReleasePhase()) {
// Process the retirements first
for(int j = 0; j<currentNumRetireBuffer; j++) {
int target = retireBuffer[j];
int i = 0;
// Find first match
for (i=0; i<currentNumNoteBuffer; i++) {
if (noteBuffer[i] == target) break;
}
if (i == currentNumNoteBuffer) { // not found
continue;
}
shiftNoteBuffer(i);
currentNumNoteBuffer--;
}
// Clear Retire Buffer
currentNumRetireBuffer = 0;
for (int i=0; i<NUMNOTEBUFFER; i++) { retireBuffer[i] = 0; }
} /* else {
Retirements should not happen in Release Phase
to keep the arpeggio playing during the release.
} */
if (noteBuffer[currentArpeggioFrame] == noteNumber || noteBuffer[currentArpeggioFrame] == 0) {
// Normally 'currentArpaggioFrame' will be updated every frame with
// 'noteBuffer[currentArpeggioFrame] == noteNumber' condition,
// but when the retirement happens currentArpeggioFrame may point at the slot already gone.
// to handle this situation the condition 'noteBuffer[currentArpeggioFrame] == 0' is added.
currentArpeggioFrame++;
if (currentArpeggioFrame >= currentNumNoteBuffer) {
currentArpeggioFrame = 0;
}
} /* else {
In cases like retirement happens, or new arpeggio note is added
the note at currentArpeggioFrame is no longer the same,
so currentArpeggioFrame doesn't have to be updated.
Moreover, updating currentArpeggioFrame in this case
often results in sounding the same note over two frames.
} */
noteNumber = noteBuffer[currentArpeggioFrame];
while (arpeggioFrameTimer >= arpeggioFrameLength)
{
arpeggioFrameTimer -= arpeggioFrameLength;
}
}
}
};
bool TonalVoice::isInReleasePhase() {
if (settingRefs->isVolumeSequenceEnabled()) {
return settingRefs->volumeSequence.isInRelease(currentVolumeSequenceFrame);
} else {
return envelopePhase == kEnvelopePhaseR;
}
}