mirror of
https://github.com/yokemura/Magical8bitPlug2.git
synced 2025-05-24 23:00:21 -04:00
316 lines
9.8 KiB
C++
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;
|
|
}
|
|
}
|