--- linuxsampler/trunk/src/engines/common/AbstractVoice.cpp 2014/09/06 18:56:02 2673 +++ linuxsampler/trunk/src/engines/common/AbstractVoice.cpp 2019/10/01 10:50:29 3616 @@ -5,6 +5,7 @@ * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2013-2017 Christian Schoenebeck and Andreas Persson * * * * 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 * @@ -28,9 +29,9 @@ AbstractVoice::AbstractVoice(SignalUnitRack* pRack): pSignalUnitRack(pRack) { pEngineChannel = NULL; - pLFO1 = new LFOUnsigned(1.0f); // amplitude LFO (0..1 range) - pLFO2 = new LFOUnsigned(1.0f); // filter LFO (0..1 range) - pLFO3 = new LFOSigned(1200.0f); // pitch LFO (-1200..+1200 range) + pLFO1 = new LFOSineUnsigned(1.0f); // amplitude LFO (0..1 range) + pLFO2 = new LFOSineUnsigned(1.0f); // filter LFO (0..1 range) + pLFO3 = new LFOSineSigned(1200.0f); // pitch LFO (-1200..+1200 range) PlaybackState = playback_state_end; SynthesisMode = 0; // set all mode bits to 0 first // select synthesis implementation (asm core is not supported ATM) @@ -110,13 +111,12 @@ #endif // CONFIG_DEVMODE Type = VoiceType; - MIDIKey = itNoteOnEvent->Param.Note.Key; - MIDIVelocity = itNoteOnEvent->Param.Note.Velocity; + pNote = pEngineChannel->pEngine->NoteByID( itNoteOnEvent->Param.Note.ID ); PlaybackState = playback_state_init; // mark voice as triggered, but no audio rendered yet Delay = itNoteOnEvent->FragmentPos(); itTriggerEvent = itNoteOnEvent; itKillEvent = Pool::Iterator(); - MidiKeyBase* pKeyInfo = GetMidiKeyInfo(MIDIKey); + MidiKeyBase* pKeyInfo = GetMidiKeyInfo(MIDIKey()); pGroupEvents = iKeyGroup ? pEngineChannel->ActiveKeyGroups[iKeyGroup] : 0; @@ -129,7 +129,7 @@ AboutToTrigger(); // calculate volume - const double velocityAttenuation = GetVelocityAttenuation(itNoteOnEvent->Param.Note.Velocity); + const double velocityAttenuation = GetVelocityAttenuation(MIDIVelocity()); float volume = CalculateVolume(velocityAttenuation) * pKeyInfo->Volume; if (volume <= 0) return -1; @@ -139,14 +139,22 @@ SYNTHESIS_MODE_SET_BITDEPTH24(SynthesisMode, SmplInfo.BitDepth == 24); // get starting crossfade volume level - float crossfadeVolume = CalculateCrossfadeVolume(itNoteOnEvent->Param.Note.Velocity); + float crossfadeVolume = CalculateCrossfadeVolume(MIDIVelocity()); VolumeLeft = volume * pKeyInfo->PanLeft; VolumeRight = volume * pKeyInfo->PanRight; - float subfragmentRate = GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE; + // this rate is used for rather mellow volume fades + const float subfragmentRate = GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE; + // this rate is used for very fast volume fades + const float quickRampRate = RTMath::Min(subfragmentRate, GetEngine()->SampleRate * 0.001f /* approx. 13ms */); CrossfadeSmoother.trigger(crossfadeVolume, subfragmentRate); + VolumeSmoother.trigger(pEngineChannel->MidiVolume, subfragmentRate); + NoteVolume.setCurveOnly(pNote ? pNote->Override.VolumeCurve : DEFAULT_FADE_CURVE); + NoteVolume.setCurrentValue(pNote ? pNote->Override.Volume.Value : 1.f); + NoteVolume.setDefaultDuration(pNote ? pNote->Override.VolumeTime : DEFAULT_NOTE_VOLUME_TIME_S); + NoteVolume.setFinal(pNote ? pNote->Override.Volume.Final : false); // Check if the sample needs disk streaming or is too short for that long cachedsamples = GetSampleCacheSize() / SmplInfo.FrameSize; @@ -170,7 +178,7 @@ RAMLoop = (SmplInfo.HasLoops && (SmplInfo.LoopStart + SmplInfo.LoopLength) <= MaxRAMPos); if (OrderNewStream()) return -1; - dmsg(4,("Disk voice launched (cached samples: %d, total Samples: %d, MaxRAMPos: %d, RAMLooping: %s)\n", cachedsamples, SmplInfo.TotalFrameCount, MaxRAMPos, (RAMLoop) ? "yes" : "no")); + dmsg(4,("Disk voice launched (cached samples: %ld, total Samples: %d, MaxRAMPos: %lu, RAMLooping: %s)\n", cachedsamples, SmplInfo.TotalFrameCount, MaxRAMPos, (RAMLoop) ? "yes" : "no")); } else { // RAM only voice MaxRAMPos = cachedsamples; @@ -186,26 +194,53 @@ } Pitch = CalculatePitchInfo(PitchBend); + NotePitch.setCurveOnly(pNote ? pNote->Override.PitchCurve : DEFAULT_FADE_CURVE); + NotePitch.setCurrentValue(pNote ? pNote->Override.Pitch.Value : 1.0f); + NotePitch.setFinal(pNote ? pNote->Override.Pitch.Final : false); + NotePitch.setDefaultDuration(pNote ? pNote->Override.PitchTime : DEFAULT_NOTE_PITCH_TIME_S); + NoteCutoff.Value = (pNote) ? pNote->Override.Cutoff.Value : 1.0f; + NoteCutoff.Final = (pNote) ? pNote->Override.Cutoff.isFinal() : false; + NoteResonance.Value = (pNote) ? pNote->Override.Resonance.Value : 1.0f; + NoteResonance.Final = (pNote) ? pNote->Override.Resonance.Final : false; // the length of the decay and release curves are dependent on the velocity - const double velrelease = 1 / GetVelocityRelease(itNoteOnEvent->Param.Note.Velocity); + const double velrelease = 1 / GetVelocityRelease(MIDIVelocity()); if (pSignalUnitRack == NULL) { // setup EG 1 (VCA EG) // get current value of EG1 controller - double eg1controllervalue = GetEG1ControllerValue(itNoteOnEvent->Param.Note.Velocity); + double eg1controllervalue = GetEG1ControllerValue(MIDIVelocity()); // calculate influence of EG1 controller on EG1's parameters EGInfo egInfo = CalculateEG1ControllerInfluence(eg1controllervalue); - TriggerEG1(egInfo, velrelease, velocityAttenuation, GetEngine()->SampleRate, itNoteOnEvent->Param.Note.Velocity); + if (pNote) { + pNote->Override.Attack.applyTo(egInfo.Attack); + pNote->Override.Decay.applyTo(egInfo.Decay); + pNote->Override.Release.applyTo(egInfo.Release); + } + + TriggerEG1(egInfo, velrelease, velocityAttenuation, GetEngine()->SampleRate, MIDIVelocity()); } else { pSignalUnitRack->Trigger(); } - uint8_t pan = MIDIPan; - if (pSignalUnitRack) pan = pSignalUnitRack->GetEndpointUnit()->CalculatePan(MIDIPan); - PanLeftSmoother.trigger(AbstractEngine::PanCurve[128 - pan], subfragmentRate); - PanRightSmoother.trigger(AbstractEngine::PanCurve[pan], subfragmentRate); + const uint8_t pan = (pSignalUnitRack) ? pSignalUnitRack->GetEndpointUnit()->CalculatePan(MIDIPan) : MIDIPan; + for (int c = 0; c < 2; ++c) { + float value = (pNote) ? AbstractEngine::PanCurveValueNorm(pNote->Override.Pan.Value, c) : 1.f; + NotePan[c].setCurveOnly(pNote ? pNote->Override.PanCurve : DEFAULT_FADE_CURVE); + NotePan[c].setCurrentValue(value); + NotePan[c].setFinal(pNote ? pNote->Override.Pan.Final : false); + NotePan[c].setDefaultDuration(pNote ? pNote->Override.PanTime : DEFAULT_NOTE_PAN_TIME_S); + } + + PanLeftSmoother.trigger( + AbstractEngine::PanCurve[128 - pan], + quickRampRate //NOTE: maybe we should have 2 separate pan smoothers, one for MIDI CC10 (with slow rate) and one for instrument script change_pan() calls (with fast rate) + ); + PanRightSmoother.trigger( + AbstractEngine::PanCurve[pan], + quickRampRate //NOTE: maybe we should have 2 separate pan smoothers, one for MIDI CC10 (with slow rate) and one for instrument script change_pan() calls (with fast rate) + ); #ifdef CONFIG_INTERPOLATE_VOLUME // setup initial volume in synthesis parameters @@ -217,15 +252,23 @@ else #else { - float finalVolume; + float finalVolume = pEngineChannel->MidiVolume * crossfadeVolume; + float fModVolume; if (pSignalUnitRack == NULL) { - finalVolume = pEngineChannel->MidiVolume * crossfadeVolume * pEG1->getLevel(); + fModVolume = pEG1->getLevel(); } else { - finalVolume = pEngineChannel->MidiVolume * crossfadeVolume * pSignalUnitRack->GetEndpointUnit()->GetVolume(); + fModVolume = pSignalUnitRack->GetEndpointUnit()->GetVolume(); } + NoteVolume.applyCurrentValueTo(fModVolume); + finalVolume *= fModVolume; + + float panL = PanLeftSmoother.render(); + float panR = PanRightSmoother.render(); + NotePan[0].applyCurrentValueTo(panL); + NotePan[1].applyCurrentValueTo(panR); - finalSynthesisParameters.fFinalVolumeLeft = finalVolume * VolumeLeft * PanLeftSmoother.render(); - finalSynthesisParameters.fFinalVolumeRight = finalVolume * VolumeRight * PanRightSmoother.render(); + finalSynthesisParameters.fFinalVolumeLeft = finalVolume * VolumeLeft * panL; + finalSynthesisParameters.fFinalVolumeRight = finalVolume * VolumeRight * panR; } #endif #endif @@ -234,12 +277,18 @@ // setup EG 2 (VCF Cutoff EG) { // get current value of EG2 controller - double eg2controllervalue = GetEG2ControllerValue(itNoteOnEvent->Param.Note.Velocity); + double eg2controllervalue = GetEG2ControllerValue(MIDIVelocity()); // calculate influence of EG2 controller on EG2's parameters EGInfo egInfo = CalculateEG2ControllerInfluence(eg2controllervalue); - TriggerEG2(egInfo, velrelease, velocityAttenuation, GetEngine()->SampleRate, itNoteOnEvent->Param.Note.Velocity); + if (pNote) { + pNote->Override.CutoffAttack.applyTo(egInfo.Attack); + pNote->Override.CutoffDecay.applyTo(egInfo.Decay); + pNote->Override.CutoffRelease.applyTo(egInfo.Release); + } + + TriggerEG2(egInfo, velrelease, velocityAttenuation, GetEngine()->SampleRate, MIDIVelocity()); } @@ -248,7 +297,7 @@ // if portamento mode is on, we dedicate EG3 purely for portamento, otherwise if portamento is off we do as told by the patch bool bPortamento = pEngineChannel->PortamentoMode && pEngineChannel->PortamentoPos >= 0.0f; float eg3depth = (bPortamento) - ? RTMath::CentsToFreqRatio((pEngineChannel->PortamentoPos - (float) MIDIKey) * 100) + ? RTMath::CentsToFreqRatio((pEngineChannel->PortamentoPos - (float) MIDIKey()) * 100) : RTMath::CentsToFreqRatio(RgnInfo.EG3Depth); float eg3time = (bPortamento) ? pEngineChannel->PortamentoTime @@ -298,7 +347,7 @@ VCFResonanceCtrl.value = pEngineChannel->ControllerTable[VCFResonanceCtrl.controller]; // calculate cutoff frequency - CutoffBase = CalculateCutoffBase(itNoteOnEvent->Param.Note.Velocity); + CutoffBase = CalculateCutoffBase(MIDIVelocity()); VCFCutoffCtrl.fvalue = CalculateFinalCutoff(CutoffBase); @@ -323,8 +372,19 @@ } void AbstractVoice::SetSampleStartOffset() { - finalSynthesisParameters.dPos = RgnInfo.SampleStartOffset; // offset where we should start playback of sample (0 - 2000 sample points) - Pos = RgnInfo.SampleStartOffset; + double pos = RgnInfo.SampleStartOffset; // offset where we should start playback of sample + + // if another sample playback start position was requested by instrument + // script (built-in script function play_note()) + if (pNote && pNote->Override.SampleOffset >= 0) { + double overridePos = + double(SmplInfo.SampleRate) * double(pNote->Override.SampleOffset) / 1000000.0; + if (overridePos < SmplInfo.TotalFrameCount) + pos = overridePos; + } + + finalSynthesisParameters.dPos = pos; + Pos = pos; } /** @@ -353,7 +413,7 @@ } AbstractEngineChannel* pChannel = pEngineChannel; - MidiKeyBase* pMidiKeyInfo = GetMidiKeyInfo(MIDIKey); + MidiKeyBase* pMidiKeyInfo = GetMidiKeyInfo(MIDIKey()); const bool bVoiceRequiresDedicatedRouting = pEngineChannel->GetFxSendCount() > 0 && @@ -379,7 +439,7 @@ RTList::Iterator itCCEvent = pChannel->pEvents->first(); RTList::Iterator itNoteEvent; - GetFirstEventOnKey(MIDIKey, itNoteEvent); + GetFirstEventOnKey(HostKey(), itNoteEvent); RTList::Iterator itGroupEvent; if (pGroupEvents && !Orphan) itGroupEvent = pGroupEvents->first(); @@ -399,7 +459,7 @@ } } - uint killPos; + uint killPos = 0; if (itKillEvent) { int maxFadeOutPos = Samples - GetEngine()->GetMinFadeOutSamples(); if (maxFadeOutPos < 0) { @@ -431,11 +491,12 @@ processCCEvents(itCCEvent, iSubFragmentEnd); uint8_t pan = MIDIPan; if (pSignalUnitRack != NULL) pan = pSignalUnitRack->GetEndpointUnit()->CalculatePan(MIDIPan); - + PanLeftSmoother.update(AbstractEngine::PanCurve[128 - pan]); PanRightSmoother.update(AbstractEngine::PanCurve[pan]); finalSynthesisParameters.fFinalPitch = Pitch.PitchBase * Pitch.PitchBend; + float fFinalVolume = VolumeSmoother.render() * CrossfadeSmoother.render(); #ifdef CONFIG_PROCESS_MUTED_CHANNELS if (pChannel->GetMute()) fFinalVolume = 0; @@ -444,7 +505,10 @@ // process transition events (note on, note off & sustain pedal) processTransitionEvents(itNoteEvent, iSubFragmentEnd); processGroupEvents(itGroupEvent, iSubFragmentEnd); - + + float fModVolume = 1; + float fModPitch = 1; + if (pSignalUnitRack == NULL) { // if the voice was killed in this subfragment, or if the // filter EG is finished, switch EG1 to fade out stage @@ -458,16 +522,16 @@ // process envelope generators switch (pEG1->getSegmentType()) { case EG::segment_lin: - fFinalVolume *= pEG1->processLin(); + fModVolume *= pEG1->processLin(); break; case EG::segment_exp: - fFinalVolume *= pEG1->processExp(); + fModVolume *= pEG1->processExp(); break; case EG::segment_end: - fFinalVolume *= pEG1->getLevel(); + fModVolume *= pEG1->getLevel(); break; // noop case EG::segment_pow: - fFinalVolume *= pEG1->processPow(); + fModVolume *= pEG1->processPow(); break; } switch (pEG2->getSegmentType()) { @@ -484,12 +548,12 @@ fFinalCutoff *= pEG2->processPow(); break; } - if (EG3.active()) finalSynthesisParameters.fFinalPitch *= EG3.render(); + if (EG3.active()) fModPitch *= EG3.render(); // process low frequency oscillators - if (bLFO1Enabled) fFinalVolume *= (1.0f - pLFO1->render()); + if (bLFO1Enabled) fModVolume *= (1.0f - pLFO1->render()); if (bLFO2Enabled) fFinalCutoff *= (1.0f - pLFO2->render()); - if (bLFO3Enabled) finalSynthesisParameters.fFinalPitch *= RTMath::CentsToFreqRatio(pLFO3->render()); + if (bLFO3Enabled) fModPitch *= RTMath::CentsToFreqRatio(pLFO3->render()); } else { // if the voice was killed in this subfragment, enter fade out stage if (itKillEvent && killPos <= iSubFragmentEnd) { @@ -509,11 +573,18 @@ fFinalCutoff = pSignalUnitRack->GetEndpointUnit()->CalculateFilterCutoff(fFinalCutoff); fFinalResonance = pSignalUnitRack->GetEndpointUnit()->CalculateResonance(fFinalResonance); - finalSynthesisParameters.fFinalPitch = - pSignalUnitRack->GetEndpointUnit()->CalculatePitch(finalSynthesisParameters.fFinalPitch); - + fModPitch = pSignalUnitRack->GetEndpointUnit()->CalculatePitch(fModPitch); } - + + NoteVolume.renderApplyTo(fModVolume); + NotePitch.renderApplyTo(fModPitch); + NoteCutoff.applyTo(fFinalCutoff); + NoteResonance.applyTo(fFinalResonance); + + fFinalVolume *= fModVolume; + + finalSynthesisParameters.fFinalPitch *= fModPitch; + // limit the pitch so we don't read outside the buffer finalSynthesisParameters.fFinalPitch = RTMath::Min(finalSynthesisParameters.fFinalPitch, float(1 << CONFIG_MAX_PITCH)); @@ -532,18 +603,24 @@ // prepare final synthesis parameters structure finalSynthesisParameters.uiToGo = iSubFragmentEnd - i; + + float panL = PanLeftSmoother.render(); + float panR = PanRightSmoother.render(); + NotePan[0].renderApplyTo(panL); + NotePan[1].renderApplyTo(panR); + #ifdef CONFIG_INTERPOLATE_VOLUME finalSynthesisParameters.fFinalVolumeDeltaLeft = - (fFinalVolume * VolumeLeft * PanLeftSmoother.render() - + (fFinalVolume * VolumeLeft * panL - finalSynthesisParameters.fFinalVolumeLeft) / finalSynthesisParameters.uiToGo; finalSynthesisParameters.fFinalVolumeDeltaRight = - (fFinalVolume * VolumeRight * PanRightSmoother.render() - + (fFinalVolume * VolumeRight * panR - finalSynthesisParameters.fFinalVolumeRight) / finalSynthesisParameters.uiToGo; #else finalSynthesisParameters.fFinalVolumeLeft = - fFinalVolume * VolumeLeft * PanLeftSmoother.render(); + fFinalVolume * VolumeLeft * panL; finalSynthesisParameters.fFinalVolumeRight = - fFinalVolume * VolumeRight * PanRightSmoother.render(); + fFinalVolume * VolumeRight * panR; #endif // render audio for one subfragment if (!delay) RunSynthesisFunction(SynthesisMode, &finalSynthesisParameters, &loop); @@ -619,22 +696,29 @@ */ void AbstractVoice::processCCEvents(RTList::Iterator& itEvent, uint End) { for (; itEvent && itEvent->FragmentPos() <= End; ++itEvent) { - if (itEvent->Type == Event::type_control_change && itEvent->Param.CC.Controller) { // if (valid) MIDI control change event + if ((itEvent->Type == Event::type_control_change || itEvent->Type == Event::type_channel_pressure) + && itEvent->Param.CC.Controller) // if (valid) MIDI control change event + { if (itEvent->Param.CC.Controller == VCFCutoffCtrl.controller) { ProcessCutoffEvent(itEvent); } if (itEvent->Param.CC.Controller == VCFResonanceCtrl.controller) { processResonanceEvent(itEvent); } + if (itEvent->Param.CC.Controller == CTRL_TABLE_IDX_AFTERTOUCH || + itEvent->Type == Event::type_channel_pressure) + { + ProcessChannelPressureEvent(itEvent); + } if (pSignalUnitRack == NULL) { if (itEvent->Param.CC.Controller == pLFO1->ExtController) { - pLFO1->update(itEvent->Param.CC.Value); + pLFO1->updateByMIDICtrlValue(itEvent->Param.CC.Value); } if (itEvent->Param.CC.Controller == pLFO2->ExtController) { - pLFO2->update(itEvent->Param.CC.Value); + pLFO2->updateByMIDICtrlValue(itEvent->Param.CC.Value); } if (itEvent->Param.CC.Controller == pLFO3->ExtController) { - pLFO3->update(itEvent->Param.CC.Value); + pLFO3->updateByMIDICtrlValue(itEvent->Param.CC.Value); } } if (itEvent->Param.CC.Controller == 7) { // volume @@ -644,8 +728,6 @@ } } else if (itEvent->Type == Event::type_pitchbend) { // if pitch bend event processPitchEvent(itEvent); - } else if (itEvent->Type == Event::type_channel_pressure) { - ProcessChannelPressureEvent(itEvent); } else if (itEvent->Type == Event::type_note_pressure) { ProcessPolyphonicKeyPressureEvent(itEvent); } @@ -672,8 +754,8 @@ } /** - * Process given list of MIDI note on, note off and sustain pedal events - * for the given time. + * Process given list of MIDI note on, note off, sustain pedal events and + * note synthesis parameter events for the given time. * * @param itEvent - iterator pointing to the next event to be processed * @param End - youngest time stamp where processing should be stopped @@ -682,9 +764,9 @@ for (; itEvent && itEvent->FragmentPos() <= End; ++itEvent) { // some voice types ignore note off if (!(Type & (Voice::type_one_shot | Voice::type_release_trigger | Voice::type_controller_triggered))) { - if (itEvent->Type == Event::type_release) { + if (itEvent->Type == Event::type_release_key) { EnterReleaseStage(); - } else if (itEvent->Type == Event::type_cancel_release) { + } else if (itEvent->Type == Event::type_cancel_release_key) { if (pSignalUnitRack == NULL) { pEG1->update(EG::event_cancel_release, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); pEG2->update(EG::event_cancel_release, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); @@ -693,6 +775,112 @@ } } } + // process stop-note events (caused by built-in instrument script function note_off()) + if (itEvent->Type == Event::type_release_note && pNote && + pEngineChannel->pEngine->NoteByID( itEvent->Param.Note.ID ) == pNote) + { + EnterReleaseStage(); + } + // process kill-note events (caused by built-in instrument script function fade_out()) + if (itEvent->Type == Event::type_kill_note && pNote && + pEngineChannel->pEngine->NoteByID( itEvent->Param.Note.ID ) == pNote) + { + Kill(itEvent); + } + // process synthesis parameter events (caused by built-in realt-time instrument script functions) + if (itEvent->Type == Event::type_note_synth_param && pNote && + pEngineChannel->pEngine->NoteByID( itEvent->Param.NoteSynthParam.NoteID ) == pNote) + { + switch (itEvent->Param.NoteSynthParam.Type) { + case Event::synth_param_volume: + NoteVolume.fadeTo(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + NoteVolume.setFinal(itEvent->Param.NoteSynthParam.isFinal()); + break; + case Event::synth_param_volume_time: + NoteVolume.setDefaultDuration(itEvent->Param.NoteSynthParam.AbsValue); + break; + case Event::synth_param_volume_curve: + NoteVolume.setCurve((fade_curve_t)itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + break; + case Event::synth_param_pitch: + NotePitch.fadeTo(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + NotePitch.setFinal(itEvent->Param.NoteSynthParam.isFinal()); + break; + case Event::synth_param_pitch_time: + NotePitch.setDefaultDuration(itEvent->Param.NoteSynthParam.AbsValue); + break; + case Event::synth_param_pitch_curve: + NotePitch.setCurve((fade_curve_t)itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + break; + case Event::synth_param_pan: + NotePan[0].fadeTo( + AbstractEngine::PanCurveValueNorm(itEvent->Param.NoteSynthParam.AbsValue, 0 /*left*/), + GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE + ); + NotePan[1].fadeTo( + AbstractEngine::PanCurveValueNorm(itEvent->Param.NoteSynthParam.AbsValue, 1 /*right*/), + GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE + ); + NotePan[0].setFinal(itEvent->Param.NoteSynthParam.isFinal()); + NotePan[1].setFinal(itEvent->Param.NoteSynthParam.isFinal()); + break; + case Event::synth_param_pan_time: + NotePan[0].setDefaultDuration(itEvent->Param.NoteSynthParam.AbsValue); + NotePan[1].setDefaultDuration(itEvent->Param.NoteSynthParam.AbsValue); + break; + case Event::synth_param_pan_curve: + NotePan[0].setCurve((fade_curve_t)itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + NotePan[1].setCurve((fade_curve_t)itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + break; + case Event::synth_param_cutoff: + NoteCutoff.Value = itEvent->Param.NoteSynthParam.AbsValue; + NoteCutoff.Final = itEvent->Param.NoteSynthParam.isFinal(); + break; + case Event::synth_param_resonance: + NoteResonance.Value = itEvent->Param.NoteSynthParam.AbsValue; + NoteResonance.Final = itEvent->Param.NoteSynthParam.isFinal(); + break; + case Event::synth_param_amp_lfo_depth: + pLFO1->setScriptDepthFactor( + itEvent->Param.NoteSynthParam.AbsValue, + itEvent->Param.NoteSynthParam.isFinal() + ); + break; + case Event::synth_param_amp_lfo_freq: + if (itEvent->Param.NoteSynthParam.isFinal()) + pLFO1->setScriptFrequencyFinal(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + else + pLFO1->setScriptFrequencyFactor(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + break; + case Event::synth_param_cutoff_lfo_depth: + pLFO2->setScriptDepthFactor( + itEvent->Param.NoteSynthParam.AbsValue, + itEvent->Param.NoteSynthParam.isFinal() + ); + break; + case Event::synth_param_cutoff_lfo_freq: + if (itEvent->Param.NoteSynthParam.isFinal()) + pLFO2->setScriptFrequencyFinal(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + else + pLFO2->setScriptFrequencyFactor(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + break; + case Event::synth_param_pitch_lfo_depth: + pLFO3->setScriptDepthFactor( + itEvent->Param.NoteSynthParam.AbsValue, + itEvent->Param.NoteSynthParam.isFinal() + ); + break; + case Event::synth_param_pitch_lfo_freq: + pLFO3->setScriptFrequencyFactor(itEvent->Param.NoteSynthParam.AbsValue, GetEngine()->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + break; + + case Event::synth_param_attack: + case Event::synth_param_decay: + case Event::synth_param_sustain: + case Event::synth_param_release: + break; // noop + } + } } } @@ -719,7 +907,7 @@ void AbstractVoice::UpdatePortamentoPos(Pool::Iterator& itNoteOffEvent) { if (pSignalUnitRack == NULL) { const float fFinalEG3Level = EG3.level(itNoteOffEvent->FragmentPos()); - pEngineChannel->PortamentoPos = (float) MIDIKey + RTMath::FreqRatioToCents(fFinalEG3Level) * 0.01f; + pEngineChannel->PortamentoPos = (float) MIDIKey() + RTMath::FreqRatioToCents(fFinalEG3Level) * 0.01f; } else { // TODO: } @@ -746,12 +934,17 @@ Voice::PitchInfo AbstractVoice::CalculatePitchInfo(int PitchBend) { PitchInfo pitch; - double pitchbasecents = InstrInfo.FineTune + RgnInfo.FineTune + GetEngine()->ScaleTuning[MIDIKey % 12]; + double pitchbasecents = InstrInfo.FineTune + RgnInfo.FineTune + GetEngine()->ScaleTuning[MIDIKey() % 12]; // GSt behaviour: maximum transpose up is 40 semitones. If // MIDI key is more than 40 semitones above unity note, // the transpose is not done. - if (!SmplInfo.Unpitched && (MIDIKey - (int) RgnInfo.UnityNote) < 40) pitchbasecents += (MIDIKey - (int) RgnInfo.UnityNote) * 100; + // + // Update: Removed this GSt misbehavior. I don't think that any stock + // gig sound requires it to resemble its original sound. + // -- Christian, 2017-07-09 + if (!SmplInfo.Unpitched /* && (MIDIKey() - (int) RgnInfo.UnityNote) < 40*/) + pitchbasecents += (MIDIKey() - (int) RgnInfo.UnityNote) * 100; pitch.PitchBase = RTMath::CentsToFreqRatioUnlimited(pitchbasecents) * (double(SmplInfo.SampleRate) / double(GetEngine()->SampleRate)); pitch.PitchBendRange = 1.0 / 8192.0 * 100.0 * InstrInfo.PitchbendRange; @@ -762,12 +955,17 @@ void AbstractVoice::onScaleTuningChanged() { PitchInfo pitch = this->Pitch; - double pitchbasecents = InstrInfo.FineTune + RgnInfo.FineTune + GetEngine()->ScaleTuning[MIDIKey % 12]; + double pitchbasecents = InstrInfo.FineTune + RgnInfo.FineTune + GetEngine()->ScaleTuning[MIDIKey() % 12]; // GSt behaviour: maximum transpose up is 40 semitones. If // MIDI key is more than 40 semitones above unity note, // the transpose is not done. - if (!SmplInfo.Unpitched && (MIDIKey - (int) RgnInfo.UnityNote) < 40) pitchbasecents += (MIDIKey - (int) RgnInfo.UnityNote) * 100; + // + // Update: Removed this GSt misbehavior. I don't think that any stock + // gig sound requires it to resemble its original sound. + // -- Christian, 2017-07-09 + if (!SmplInfo.Unpitched /* && (MIDIKey() - (int) RgnInfo.UnityNote) < 40*/) + pitchbasecents += (MIDIKey() - (int) RgnInfo.UnityNote) * 100; pitch.PitchBase = RTMath::CentsToFreqRatioUnlimited(pitchbasecents) * (double(SmplInfo.SampleRate) / double(GetEngine()->SampleRate)); this->Pitch = pitch; @@ -784,7 +982,7 @@ // the volume of release triggered samples depends on note length if (Type & Voice::type_release_trigger) { float noteLength = float(GetEngine()->FrameTime + Delay - - GetNoteOnTime(MIDIKey) ) / GetEngine()->SampleRate; + GetNoteOnTime(MIDIKey()) ) / GetEngine()->SampleRate; volume *= GetReleaseTriggerAttenuation(noteLength); }