diff --git a/trunk/src/engines/EngineChannelBase.h b/trunk/src/engines/EngineChannelBase.h index 406d286..e7276e9 100644 --- a/trunk/src/engines/EngineChannelBase.h +++ b/trunk/src/engines/EngineChannelBase.h @@ -428,6 +428,107 @@ namespace LinuxSampler { template friend class EngineBase; + /** + * Handle key group (a.k.a. exclusive group) conflicts. + * @param SelfMask - Without self-masking notes turn off notes with the same pitch + * regardless of velocity. With self-masking higher-velocity notes + * turn off lower-velocity notes, but lower-velocity notes + * do not turn off higher-velocity notes. + * The case of notes with the same velocity is undocumented. + * @param NotePolyphony - Maximum number of voices playing simultaneously triggered by the same key. + * @param GroupPolyphony - Maximum number of voices playing simultaneously within the same group. + * @param ForceGroup - Set true only for sfz to force checking GroupPolyphony even + * on voices played on the same note. + * @param ForceOffBy - Set true only for sfz to force voice relase with off_by opcode. + * @param NewAlgorithm - TODO: remove this argument if it makes into the trunk. + */ + void HandleKeyGroupConflicts(int KeyGroup, Pool::Iterator& itNoteOnEvent, bool SelfMask = false, int NotePolyphony = 1, int GroupPolyphony = 1, bool ForceGroup = false, bool ForceOffBy = false, bool NewAlgorithm = false) { + dmsg(4,("EngineChannelBase::HandleKeyGroupConflicts KeyGroup=%d\n", KeyGroup)); + if (NewAlgorithm) { + // Gather all active voices that might be released + // because of a group conflict, ordered by trigger time + typedef std::multimap::RTListVoiceIterator> VoiceMap; + VoiceMap OrderedVoices; + + // iterate through all active keys + for (Pool::Iterator iuiKey = this->pActiveKeys->first(); + iuiKey != this->pActiveKeys->end(); + ++iuiKey) + { + MidiKey* pKey = &this->pMIDIKeyInfo[*iuiKey]; + // and active notes for each key + for (typename MidiKeyboardManager::RTListNoteIterator itNote = pKey->pActiveNotes->first(); + itNote != pKey->pActiveNotes->end(); + ++itNote) + { + // and active voices for each note + for (typename MidiKeyboardManager::RTListVoiceIterator itVoice = itNote->pActiveVoices->first(); + itVoice != itNote->pActiveVoices->end(); + ++itVoice) + { + // Add voice to the multimap if it's in the same group + // (to check for NotePolyphony and GroupPolyphony) + // or if it should be stopped by off_by opcode (sfz) + if (itVoice->GetKeyGroup() == KeyGroup || itVoice->GetOffBy() == KeyGroup) + { + OrderedVoices.insert(typename VoiceMap::value_type( + itVoice->pNote->triggerSchedTime, + itVoice + )); + } + } + } + } + + int NoteCount = 0; + int GroupCount = 0; + + // Iterate in reverse order to save most recent voices and + // release those triggered earlier + for (typename VoiceMap::reverse_iterator itMap = OrderedVoices.rbegin(); + itMap != OrderedVoices.rend(); + ++itMap) + { + typename MidiKeyboardManager::RTListVoiceIterator itVoice = itMap->second; + bool releaseByNote = false; + bool releaseByGroup = false; + + if (itVoice->GetKeyGroup() == KeyGroup) { + ++GroupCount; + + if (itNoteOnEvent->Param.Note.Key == itVoice->MIDIKey()) { + ++NoteCount; + + releaseByNote = ( + (NotePolyphony > 0 && NoteCount >= NotePolyphony) && ( + !SelfMask || ( + SelfMask && + itNoteOnEvent->Param.Note.Velocity >= itVoice->MIDIVelocity() + ) + ) + ); + } + if (ForceGroup || itNoteOnEvent->Param.Note.Key != itVoice->MIDIKey()) { + releaseByGroup = (GroupPolyphony > -1 && GroupCount >= GroupPolyphony); + } + } + + // Forced release + bool releaseByOffBy = ForceOffBy && itVoice->GetOffBy() == KeyGroup; + + ActiveKeyGroupMap::iterator it = ActiveKeyGroups.find(itVoice->GetOffBy()); + if (it != ActiveKeyGroups.end() && (releaseByNote || releaseByGroup || releaseByOffBy)) + { + RTList::Iterator itEvent = it->second->allocAppend(pEngine->pEventPool); + *itEvent = *itNoteOnEvent; + itEvent->Type = Event::type_release_voice; + itEvent->Param.ReleaseVoice.VoiceID = dynamic_cast*>(pEngine)->GetVoicePool()->getID(itVoice); + } + } + // !NewAlgorithm + } else AbstractEngineChannel::HandleKeyGroupConflicts(KeyGroup, itNoteOnEvent); + } + protected: EngineChannelBase() : MidiKeyboardManager(this), diff --git a/trunk/src/engines/common/AbstractVoice.cpp b/trunk/src/engines/common/AbstractVoice.cpp index 7be4e33..7114313 100644 --- a/trunk/src/engines/common/AbstractVoice.cpp +++ b/trunk/src/engines/common/AbstractVoice.cpp @@ -118,6 +118,7 @@ namespace LinuxSampler { itKillEvent = Pool::Iterator(); MidiKeyBase* pKeyInfo = GetMidiKeyInfo(MIDIKey()); + // FIXME: in sfz iKeyGroup (off_by) = 0 is valid pGroupEvents = iKeyGroup ? pEngineChannel->ActiveKeyGroups[iKeyGroup] : 0; SmplInfo = GetSampleInfo(); diff --git a/trunk/src/engines/common/AbstractVoice.h b/trunk/src/engines/common/AbstractVoice.h index fe6b208..667cf0f 100644 --- a/trunk/src/engines/common/AbstractVoice.h +++ b/trunk/src/engines/common/AbstractVoice.h @@ -124,6 +124,10 @@ namespace LinuxSampler { /// MIDI note-on velocity value which the voice should use for calculating any synthesis relevant parameters (i.e. amplitude). inline uint8_t MIDIVelocity() const { return pNote->cause.Param.Note.Velocity; } + virtual int GetKeyGroup() = 0; + // OffBy is different only in sfz::Voice + virtual int GetOffBy() { return GetKeyGroup(); } + void processCCEvents(RTList::Iterator& itEvent, uint End); void processPitchEvent(RTList::Iterator& itEvent); void processResonanceEvent(RTList::Iterator& itEvent); diff --git a/trunk/src/engines/common/Event.h b/trunk/src/engines/common/Event.h index 1f97e21..49e5cba 100644 --- a/trunk/src/engines/common/Event.h +++ b/trunk/src/engines/common/Event.h @@ -134,6 +134,11 @@ namespace LinuxSampler { typedef pool_element_id_t note_id_t; /** + * Unique numeric ID of a voice. + */ + typedef pool_element_id_t voice_id_t; + + /** * Unique numeric ID of a script callback ID instance which can be used to * retrieve access to the actual @c ScriptEvent object. Once the script * callback instance associated with a certain ID stopped its execution @@ -169,6 +174,7 @@ namespace LinuxSampler { type_stop_note, ///< caused by a call to built-in instrument script function note_off() type_kill_note, ///< caused by a call to built-in instrument script function fade_out() type_note_synth_param, ///< change a note's synthesis parameters (upon real-time instrument script function calls, i.e. change_vol(), change_tune(), change_pan(), etc.) + type_release_voice, } Type; enum synth_param_t { synth_param_volume, @@ -244,6 +250,9 @@ namespace LinuxSampler { bool Relative; ///< Whether @c Delta should be applied relatively against the note's current synthesis parameter value (false means the paramter's current value is simply replaced by Delta). float AbsValue; ///< New current absolute value of synthesis parameter (that is after @c Delta being applied). } NoteSynthParam; + struct _ReleaseVoice { + voice_id_t VoiceID; + } ReleaseVoice; } Param; EngineChannel* pEngineChannel; ///< Pointer to the EngineChannel where this event occured on, NULL means Engine global event (e.g. SysEx message). MidiInputPort* pMidiInputPort; ///< Pointer to the MIDI input port on which this event occured (NOTE: currently only for global events, that is SysEx messages) diff --git a/trunk/src/engines/gig/Engine.cpp b/trunk/src/engines/gig/Engine.cpp index 588b3d1..a8e325b 100644 --- a/trunk/src/engines/gig/Engine.cpp +++ b/trunk/src/engines/gig/Engine.cpp @@ -226,11 +226,6 @@ namespace LinuxSampler { namespace gig { // if nothing defined for this key if (!pRegion) return Pool::Iterator(); // nothing to do - int iKeyGroup = pRegion->KeyGroup; - // only need to send a group event from the first voice in a layered region, - // as all layers in a region always belongs to the same key group - if (HandleKeyGroupConflicts && iLayer == 0) pChannel->HandleKeyGroupConflicts(iKeyGroup, itNoteOnEvent); - Voice::type_t VoiceType = Voice::type_normal; // get current dimension values to select the right dimension region @@ -364,6 +359,11 @@ namespace LinuxSampler { namespace gig { } if (!pDimRgn) return Pool::Iterator(); // error (could not resolve dimension region) + int iKeyGroup = pRegion->KeyGroup; + // only need to send a group event from the first voice in a layered region, + // as all layers in a region always belongs to the same key group + if (HandleKeyGroupConflicts && iLayer == 0) pChannel->HandleKeyGroupConflicts(iKeyGroup, itNoteOnEvent, pDimRgn->SelfMask); + // no need to continue if sample is silent if (!pDimRgn->pSample || !pDimRgn->pSample->SamplesTotal) return Pool::Iterator(); diff --git a/trunk/src/engines/gig/Voice.cpp b/trunk/src/engines/gig/Voice.cpp index c9ec41b..e9fa227 100644 --- a/trunk/src/engines/gig/Voice.cpp +++ b/trunk/src/engines/gig/Voice.cpp @@ -510,6 +510,14 @@ namespace LinuxSampler { namespace gig { void Voice::ProcessGroupEvent(RTList::Iterator& itEvent) { dmsg(4,("Voice %p processGroupEvents event type=%d", (void*)this, itEvent->Type)); + bool ReleaseVoice = ( + itEvent->Type == Event::type_release_voice && + itEvent->Param.ReleaseVoice.VoiceID == pEngine->GetVoicePool()->getID(this) + ); + bool NewNote = ( + itEvent->Type == Event::type_note_on && + itEvent->Type == itEvent->Param.Note.Key != HostKey() + ); // TODO: The SustainPedal condition could be wrong, maybe the // check should be if this Voice is in release stage or is a // release sample instead. Need to test this in GSt. @@ -519,7 +527,7 @@ namespace LinuxSampler { namespace gig { // note should be stopped at all, because it doesn't sound naturally // with a drumkit. // -- Christian, 2013-01-08 - if (itEvent->Param.Note.Key != HostKey() /*|| + if (NewNote || ReleaseVoice /*|| !GetGigEngineChannel()->SustainPedal*/) { dmsg(4,("Voice %p - kill", (void*)this)); diff --git a/trunk/src/engines/gig/Voice.h b/trunk/src/engines/gig/Voice.h index a794358..6359a1d 100644 --- a/trunk/src/engines/gig/Voice.h +++ b/trunk/src/engines/gig/Voice.h @@ -63,6 +63,7 @@ namespace LinuxSampler { namespace gig { void SetEngine(LinuxSampler::Engine* pEngine); void CalculateFadeOutCoeff(float FadeOutTime, float SampleRate); virtual release_trigger_t GetReleaseTriggerFlags() OVERRIDE; + virtual int GetKeyGroup() OVERRIDE { return pRegion->GetParent()->KeyGroup; } protected: virtual SampleInfo GetSampleInfo() OVERRIDE; diff --git a/trunk/src/engines/sf2/Voice.cpp b/trunk/src/engines/sf2/Voice.cpp index 32b1847..63069ef 100644 --- a/trunk/src/engines/sf2/Voice.cpp +++ b/trunk/src/engines/sf2/Voice.cpp @@ -343,7 +343,15 @@ namespace LinuxSampler { namespace sf2 { } void Voice::ProcessGroupEvent(RTList::Iterator& itEvent) { - if (itEvent->Param.Note.Key != HostKey()) { + bool ReleaseVoice = ( + itEvent->Type == Event::type_release_voice && + itEvent->Param.ReleaseVoice.VoiceID == pEngine->GetVoicePool()->getID(this) + ); + bool NewNote = ( + itEvent->Type == Event::type_note_on && + itEvent->Type == itEvent->Param.Note.Key != HostKey() + ); + if (NewNote || ReleaseVoice) { // kill the voice fast SignalRack.EnterFadeOutStage(); } diff --git a/trunk/src/engines/sf2/Voice.h b/trunk/src/engines/sf2/Voice.h index c902d23..3982275 100644 --- a/trunk/src/engines/sf2/Voice.h +++ b/trunk/src/engines/sf2/Voice.h @@ -56,6 +56,7 @@ namespace LinuxSampler { namespace sf2 { void SetEngine(LinuxSampler::Engine* pEngine); void CalculateFadeOutCoeff(float FadeOutTime, float SampleRate); virtual release_trigger_t GetReleaseTriggerFlags() OVERRIDE; + virtual int GetKeyGroup() OVERRIDE { return pRegion->exclusiveClass; } protected: virtual SampleInfo GetSampleInfo() OVERRIDE; diff --git a/trunk/src/engines/sfz/Engine.cpp b/trunk/src/engines/sfz/Engine.cpp index 1177503..6d6d691 100644 --- a/trunk/src/engines/sfz/Engine.cpp +++ b/trunk/src/engines/sfz/Engine.cpp @@ -273,7 +273,10 @@ namespace LinuxSampler { namespace sfz { Pool::Iterator itNewVoice; - if (HandleKeyGroupConflicts) pChannel->HandleKeyGroupConflicts(pRgn->group, itNoteOnEvent); + // TODO: check for discrepancy while loading Regions to the Instrument. + // Voices in the same group must have identical values for + // off_by, polyphony, note_polyphony and note_selfmask opcodes. + if (HandleKeyGroupConflicts) pChannel->HandleKeyGroupConflicts(pRgn->group, itNoteOnEvent, (bool)pRgn->note_selfmask, pRgn->note_polyphony, pRgn->polyphony, true, true, true); // no need to process if sample is silent if (!pRgn->GetSample(false) || !pRgn->GetSample()->GetTotalFrameCount()) return Pool::Iterator(); diff --git a/trunk/src/engines/sfz/EngineChannel.cpp b/trunk/src/engines/sfz/EngineChannel.cpp index 8f312e3..5d109d8 100644 --- a/trunk/src/engines/sfz/EngineChannel.cpp +++ b/trunk/src/engines/sfz/EngineChannel.cpp @@ -163,7 +163,6 @@ namespace LinuxSampler { namespace sfz { // rebuild ActiveKeyGroups map with key groups of current instrument for (std::vector< ::sfz::Region*>::iterator itRegion = newInstrument->regions.begin() ; itRegion != newInstrument->regions.end() ; ++itRegion) { - AddGroup((*itRegion)->group); AddGroup((*itRegion)->off_by); } diff --git a/trunk/src/engines/sfz/Voice.cpp b/trunk/src/engines/sfz/Voice.cpp index b2f2d5a..3ea1087 100644 --- a/trunk/src/engines/sfz/Voice.cpp +++ b/trunk/src/engines/sfz/Voice.cpp @@ -286,9 +286,15 @@ namespace LinuxSampler { namespace sfz { void Voice::ProcessGroupEvent(RTList::Iterator& itEvent) { dmsg(4,("Voice %p processGroupEvents event type=%d", (void*)this, itEvent->Type)); + + bool ReleaseVoice = ( + itEvent->Type == Event::type_release_voice && + itEvent->Param.ReleaseVoice.VoiceID == pEngine->GetVoicePool()->getID(this) + ); + if (itEvent->Type == Event::type_control_change || (Type & Voice::type_controller_triggered) || - itEvent->Param.Note.Key != HostKey()) { + ReleaseVoice) { dmsg(4,("Voice %p - kill", (void*)this)); if (pRegion->off_mode == ::sfz::OFF_NORMAL) { // turn off the voice by entering release envelope stage diff --git a/trunk/src/engines/sfz/Voice.h b/trunk/src/engines/sfz/Voice.h index 36ca8e6..23a4da4 100644 --- a/trunk/src/engines/sfz/Voice.h +++ b/trunk/src/engines/sfz/Voice.h @@ -54,6 +54,8 @@ namespace LinuxSampler { namespace sfz { void SetEngine(LinuxSampler::Engine* pEngine); void CalculateFadeOutCoeff(float FadeOutTime, float SampleRate); virtual release_trigger_t GetReleaseTriggerFlags() OVERRIDE; + virtual int GetKeyGroup() OVERRIDE { return pRegion->group; } + virtual int GetOffBy() OVERRIDE { return pRegion->off_by; } virtual void VoiceFreed() OVERRIDE { SignalRack.Reset(); } @@ -118,6 +120,9 @@ namespace LinuxSampler { namespace sfz { friend class EndpointUnit; friend class SfzSignalUnitRack; + // Required to support polyphony + friend class EngineChannel; + protected: virtual uint8_t CrossfadeAttenuation(uint8_t& CrossfadeControllerValue) OVERRIDE { /*uint8_t c = std::max(CrossfadeControllerValue, pRegion->AttenuationControllerThreshold); diff --git a/trunk/src/engines/sfz/sfz.cpp b/trunk/src/engines/sfz/sfz.cpp index 417bc54..2562ccd 100755 --- a/trunk/src/engines/sfz/sfz.cpp +++ b/trunk/src/engines/sfz/sfz.cpp @@ -330,8 +330,15 @@ namespace sfz trigger = TRIGGER_ATTACK; group = 0; - off_by = 0; + // http://www.sfzformat.com/legacy/ states that default off_by is 0, + // but that would make all regions "exclusive" by default. + off_by = -1; off_mode = OFF_FAST; + // -1 means no polyphony limit, 0 means no playback at all + polyphony = -1; + // 0 means no note_polyphony limit + note_polyphony = 0; + note_selfmask = SELFMASK_ON; // sample player count = optional::nothing; @@ -639,6 +646,9 @@ namespace sfz definition->group = group; definition->off_by = off_by; definition->off_mode = off_mode; + definition->polyphony = polyphony; + definition->note_polyphony = note_polyphony; + definition->note_selfmask = note_selfmask; definition->on_locc = on_locc; definition->on_hicc = on_hicc; @@ -1583,6 +1593,13 @@ namespace sfz if (value == "fast") pCurDef->off_mode = OFF_FAST; else if (value == "normal") pCurDef->off_mode = OFF_NORMAL; } + else if ("polyphony" == key) pCurDef->polyphony = ToInt(value); + else if ("note_polyphony" == key) pCurDef->note_polyphony = ToInt(value); + else if ("note_selfmask" == key) + { + if (value == "on") pCurDef->note_selfmask = SELFMASK_ON; + else if (value == "off") pCurDef->note_selfmask = SELFMASK_OFF; + } // sample player else if ("count" == key) { pCurDef->count = ToInt(value); pCurDef->loop_mode = ONE_SHOT; } diff --git a/trunk/src/engines/sfz/sfz.h b/trunk/src/engines/sfz/sfz.h index 381df2e..3c65140 100755 --- a/trunk/src/engines/sfz/sfz.h +++ b/trunk/src/engines/sfz/sfz.h @@ -143,6 +143,7 @@ namespace sfz LPF_2P, HPF_2P, BPF_2P, BRF_2P, PKF_2P, LPF_4P, HPF_4P, LPF_6P, HPF_6P }; + enum note_selfmask_t { SELFMASK_ON, SELFMASK_OFF }; typedef unsigned char trigger_t; typedef unsigned char uint8_t; @@ -427,9 +428,12 @@ namespace sfz trigger_t trigger; - uint group; - uint off_by; + int group; + int off_by; off_mode_t off_mode; + int polyphony; + int note_polyphony; + note_selfmask_t note_selfmask; Array on_locc; Array on_hicc;