diff -Naur -x CVS -x osx -x '.*' linuxsampler.orig/configure.in linuxsampler/configure.in --- linuxsampler.orig/configure.in 2008-02-09 16:51:11.000000000 +0900 +++ linuxsampler/configure.in 2008-02-10 14:08:47.000000000 +0900 @@ -878,6 +878,27 @@ ) AC_DEFINE_UNQUOTED(CONFIG_PORTAMENTO_TIME_DEFAULT, $config_portamento_time_default, [Define default portamento time.]) +AC_ARG_ENABLE(legato-ctrl, + [ --enable-legato-ctrl + MIDI control number for legato + (default=0, no legato control).], + [config_legato_ctrl="${enableval}"], + [config_legato_ctrl="0"] +) +AC_DEFINE_UNQUOTED(CONFIG_LEGATO_CTRL, $config_legato_ctrl, [MIDI control for legato]) + +AC_ARG_ENABLE(legato-crossfade-time, + [ --enable-legato-crossfade-time + Crossfade time for legato play (in seconds) + Non-negative value: fixed value + Negative value: variable value, calculated by (CC-63)* + (legato_crossfade_time)/64.0, where CC is the legato control + value (default=0.15).], + [config_legato_crossfade_time="${enableval}"], + [config_legato_crossfade_time="0.15"] +) +AC_DEFINE_UNQUOTED(CONFIG_LEGATO_CROSSFADE_TIME, $config_legato_crossfade_time, [crossfade time for legato]) + AC_ARG_ENABLE(signed-triang-algo, [ --enable-signed-triang-algo Signed triangular wave algorithm to be used (e.g. for LFOs). diff -Naur -x CVS -x osx -x '.*' linuxsampler.orig/src/common/atomic.h linuxsampler/src/common/atomic.h --- linuxsampler.orig/src/common/atomic.h 2008-01-26 00:06:01.000000000 +0900 +++ linuxsampler/src/common/atomic.h 2008-02-10 09:32:47.000000000 +0900 @@ -1249,6 +1249,8 @@ /* TODO: should use atomic routines in CoreServices.framework */ #define atomic_inc(a) (++(*a)) #define atomic_dec(a) (--(*a)) +#define atomic_add(i, a) ((*a) += (i)) +#define atomic_sub(i, a) ((*a) -= (i)) #else diff -Naur -x CVS -x osx -x '.*' linuxsampler.orig/src/engines/gig/Engine.cpp linuxsampler/src/engines/gig/Engine.cpp --- linuxsampler.orig/src/engines/gig/Engine.cpp 2008-01-24 23:14:21.000000000 +0900 +++ linuxsampler/src/engines/gig/Engine.cpp 2008-02-10 17:54:05.000000000 +0900 @@ -987,18 +987,41 @@ if (pEngineChannel->SoloMode) { Pool::Iterator itYoungestKey = pEngineChannel->pActiveKeys->last(); if (itYoungestKey) { + RTList::Iterator itVoice; const int iYoungestKey = *itYoungestKey; const midi_key_info_t* pOtherKey = &pEngineChannel->pMIDIKeyInfo[iYoungestKey]; if (pOtherKey->Active) { // get final portamento position of currently active voice - if (pEngineChannel->PortamentoMode) { - RTList::Iterator itVoice = pOtherKey->pActiveVoices->last(); - if (itVoice) itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList); + if (pEngineChannel->PortamentoMode + #if CONFIG_LEGATO_CTRL + || pEngineChannel->LegatoMode + #endif + ) { + itVoice = pOtherKey->pActiveVoices->last(); + if (itVoice) + #if CONFIG_LEGATO_CTRL + { + if (pEngineChannel->PortamentoMode) { + pEngineChannel->PortamentoTarget = key; + itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList); + } + if (pEngineChannel->LegatoMode) { + itVoice->TriggerLegatoFadeOut(itNoteOnEventOnKeyList); + pEngineChannel->bInLegato = 1; + } + } + #else + itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList); + #endif } // kill all voices on the (other) key RTList::Iterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); RTList::Iterator end = pOtherKey->pActiveVoices->end(); for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { + #if CONFIG_LEGATO_CTRL + if (pEngineChannel->LegatoMode && itVoice == itVoiceToBeKilled) + continue; + #endif if (itVoiceToBeKilled->Type != Voice::type_release_trigger) itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList); } @@ -1040,6 +1063,9 @@ for (int i = 0; i < voicesRequired; i++) LaunchVoice(pEngineChannel, itNoteOnEventOnKeyList, i, false, true, true); } + #if CONFIG_LEGATO_CTRL + pEngineChannel->bInLegato = 0; + #endif } // if neither a voice was spawned or postponed then remove note on event from key again @@ -1747,6 +1773,18 @@ } break; } + #if CONFIG_LEGATO_CTRL + case CONFIG_LEGATO_CTRL: { // legato on / off + int ival = itControlChangeEvent->Param.CC.Value; + float fval = CONFIG_LEGATO_CROSSFADE_TIME; + pEngineChannel->LegatoMode = (ival >= 64); + if (fval >= 0.0) + pEngineChannel->LegatoTime = fval; + else + pEngineChannel->LegatoTime = (-fval) * (ival - 63) / 64.0; + break; + } + #endif case 100: { // RPN controller LSB pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); break; diff -Naur -x CVS -x osx -x '.*' linuxsampler.orig/src/engines/gig/EngineChannel.h linuxsampler/src/engines/gig/EngineChannel.h --- linuxsampler.orig/src/engines/gig/EngineChannel.h 2008-02-09 16:51:12.000000000 +0900 +++ linuxsampler/src/engines/gig/EngineChannel.h 2008-02-10 09:46:15.000000000 +0900 @@ -117,6 +117,14 @@ bool PortamentoMode; ///< in Portamento Mode we slide the pitch from the last note to the current note. float PortamentoTime; ///< How long it will take to glide from the previous note to the current (in seconds) float PortamentoPos; ///< Current position on the keyboard, that is integer and fractional part (only used if PortamentoMode is on) + + #if CONFIG_LEGATO_CTRL + float PortamentoTarget; ///< Portamento destination position (only used during legato crossfade) + bool LegatoMode; ///< in Legato Mode we crossfade the volume from the last note to the current note. + float LegatoTime; ///< How long it will take to crossfade between two notes (in seconds) + bool bInLegato; + #endif + double GlobalVolume; ///< Master volume factor set through the C++ API / LSCP (a value < 1.0 means attenuation, a value > 1.0 means amplification) double MidiVolume; ///< Volume factor altered by MIDI CC#7 (a value < 1.0 means attenuation, a value > 1.0 means amplification) float GlobalPanLeft; diff -Naur -x CVS -x osx -x '.*' linuxsampler.orig/src/engines/gig/Voice.cpp linuxsampler/src/engines/gig/Voice.cpp --- linuxsampler.orig/src/engines/gig/Voice.cpp 2007-09-04 10:12:48.000000000 +0900 +++ linuxsampler/src/engines/gig/Voice.cpp 2008-02-13 19:40:30.000000000 +0900 @@ -302,6 +302,19 @@ dmsg(5,("PortamentoPos=%f, depth=%f, time=%f\n", pEngineChannel->PortamentoPos, eg3depth, eg3time)); } + #if CONFIG_LEGATO_CTRL + // setup Legato EG + if (pEngineChannel->LegatoMode && pEngineChannel->bInLegato) { + LegatoEG.trigger(0.0, pEngineChannel->LegatoTime, pEngine->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + LegatoState = legato_state_fadein; + #ifdef CONFIG_INTERPOLATE_VOLUME + finalSynthesisParameters.fFinalVolumeLeft = 0; + finalSynthesisParameters.fFinalVolumeRight = 0; + #endif + } else { + LegatoState = legato_state_disable; + } + #endif // setup LFO 1 (VCA LFO) { @@ -836,7 +849,24 @@ fFinalCutoff *= EG2.getLevel(); break; // noop } - if (EG3.active()) finalSynthesisParameters.fFinalPitch *= EG3.render(); + if (EG3.active()) { + finalSynthesisParameters.fFinalPitch *= EG3.render(); + } + + #if CONFIG_LEGATO_CTRL + if (LegatoState != legato_state_disable && LegatoState != legato_state_sustain) { + float fval = LegatoEG.render(); + if (LegatoState == legato_state_fadeout) + fval = 1.0 - fval; + if (fval > 0.75) + fval = 1.0; + else + fval = fval / 0.75; + fFinalVolume *= fval; + if (LegatoState == legato_state_fadeout) + finalSynthesisParameters.fFinalPitch *= LegatoPortamentoRatio; + } + #endif // process low frequency oscillators if (bLFO1Enabled) fFinalVolume *= (1.0f - pLFO1->render()); @@ -871,6 +901,7 @@ finalSynthesisParameters.fFinalVolumeRight = fFinalVolume * VolumeRight * PanRightSmoother.render(); #endif + // render audio for one subfragment RunSynthesisFunction(SynthesisMode, &finalSynthesisParameters, &loop); @@ -897,6 +928,22 @@ EG3.increment(1); if (!EG3.toEndLeft()) EG3.update(); // neutralize envelope coefficient if end reached + #if CONFIG_LEGATO_CTRL + if (LegatoState != legato_state_disable && LegatoState != legato_state_sustain) { + LegatoEG.increment(1); + if (!LegatoEG.toEndLeft()) { + LegatoEG.update(); + if (LegatoState == legato_state_fadein) + LegatoState = legato_state_sustain; + else { + // End of fade-out -> enter release stage of EG1 + // EG1.update(EGADSR::event_release, pEngine->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + EG1.enterFadeOutStage(); + } + } + } + #endif + Pos = newPos; i = iSubFragmentEnd; } @@ -915,6 +962,34 @@ pEngineChannel->PortamentoPos = (float) MIDIKey + RTMath::FreqRatioToCents(fFinalEG3Level) * 0.01f; } + #if CONFIG_LEGATO_CTRL + /** @brief Trigger fade-out stage for legato mode. + * + * Will be called when legato mode is enabled and a new note is pressed on. + * Triggers LegatoEG in fade-out mode and also EG3 if portamento mode is on. + */ + void Voice::TriggerLegatoFadeOut(Pool::Iterator& itNoteOffEvent) { + if (pEngineChannel->LegatoMode) { + float startLevel = 0.0; + int32_t pos = itNoteOffEvent->FragmentPos(); + if (LegatoEG.active()) { + if (LegatoState == legato_state_fadein) + startLevel = 1.0 - LegatoEG.level(pos); + else if (LegatoState == legato_state_fadeout) + startLevel = LegatoEG.level(pos); + } + LegatoEG.trigger(startLevel, pEngineChannel->LegatoTime, pEngine->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + LegatoState = legato_state_fadeout; + } + if (pEngineChannel->PortamentoMode) { + float eg3depth = RTMath::CentsToFreqRatio((pEngineChannel->PortamentoPos - pEngineChannel->PortamentoTarget) * 100); + float eg3time = pEngineChannel->PortamentoTime; + EG3.trigger(eg3depth, eg3time, pEngine->SampleRate / CONFIG_DEFAULT_SUBFRAGMENT_SIZE); + LegatoPortamentoRatio = 1.0 / eg3depth; + } else LegatoPortamentoRatio = 1.0; + } + #endif + /** * Immediately kill the voice. This method should not be used to kill * a normal, active voice, because it doesn't take care of things like diff -Naur -x CVS -x osx -x '.*' linuxsampler.orig/src/engines/gig/Voice.h linuxsampler/src/engines/gig/Voice.h --- linuxsampler.orig/src/engines/gig/Voice.h 2007-10-15 07:00:16.000000000 +0900 +++ linuxsampler/src/engines/gig/Voice.h 2008-02-12 01:14:17.000000000 +0900 @@ -122,6 +122,9 @@ inline bool IsActive() { return PlaybackState; } inline bool IsStealable() { return !itKillEvent && PlaybackState >= playback_state_ram; } void UpdatePortamentoPos(Pool::Iterator& itNoteOffEvent); + #if CONFIG_LEGATO_CTRL + void TriggerLegatoFadeOut(Pool::Iterator& itNoteOffEvent); + #endif //private: // Types @@ -159,6 +162,17 @@ EGADSR EG1; ///< Envelope Generator 1 (Amplification) EGADSR EG2; ///< Envelope Generator 2 (Filter cutoff frequency) EGDecay EG3; ///< Envelope Generator 3 (Pitch) + #if CONFIG_LEGATO_CTRL + enum legato_state_t { + legato_state_disable = 0, + legato_state_fadein = 1, + legato_state_sustain = 2, + legato_state_fadeout = 3 + }; + EGDecay LegatoEG; ///< Envelope Generator for legato mode + legato_state_t LegatoState; + float LegatoPortamentoRatio; ///< During the fade-out stage, the frequency is multiplied by EG3.render() times this value + #endif midi_ctrl VCFCutoffCtrl; midi_ctrl VCFResonanceCtrl; LFOUnsigned* pLFO1; ///< Low Frequency Oscillator 1 (Amplification)