/* ============================================================================== 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; } }