--- linuxsampler/trunk/src/engines/EngineBase.h 2016/04/20 15:22:58 2884 +++ linuxsampler/trunk/src/engines/EngineBase.h 2016/10/31 00:05:00 3034 @@ -58,7 +58,7 @@ typedef typename RTList::Iterator RootRegionIterator; typedef typename MidiKeyboardManager::MidiKey MidiKey; - EngineBase() : SuspendedRegions(128), noteIDPool(GLOBAL_MAX_NOTES) { + EngineBase() : noteIDPool(GLOBAL_MAX_NOTES), SuspendedRegions(128) { pDiskThread = NULL; pNotePool = new Pool< Note >(GLOBAL_MAX_NOTES); pNotePool->setPoolElementIDsReservedBits(INSTR_SCRIPT_EVENT_ID_RESERVED_BITS); @@ -163,6 +163,7 @@ dmsg(5,("Engine: Sysex received\n")); ProcessSysex(itEvent); break; + default: ; // noop } } } @@ -689,6 +690,9 @@ NoteIterator itNewNote = pNotePool->allocAppend(); const note_id_t newNoteID = pNotePool->getID(itNewNote); + // remember the engine's time when this note was triggered exactly + itNewNote->triggerSchedTime = pNoteOnEvent->SchedTime(); + // usually the new note (and its subsequent voices) will be // allocated on the key provided by the event's note number, // however if this new note is requested not to be a regular @@ -795,6 +799,18 @@ case Event::type_note_pressure: //TODO: ... break; + + case Event::type_sysex: + //TODO: ... + break; + + case Event::type_cancel_release_key: + case Event::type_release_key: + case Event::type_release_note: + case Event::type_play_note: + case Event::type_stop_note: + case Event::type_note_synth_param: + break; // noop } // see HACK comment above @@ -853,10 +869,18 @@ dmsg(5,("Engine: Note on received\n")); ProcessNoteOn((EngineChannel*)itEvent->pEngineChannel, itEvent); break; + case Event::type_play_note: + dmsg(5,("Engine: Play Note received\n")); + ProcessNoteOn((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; case Event::type_note_off: dmsg(5,("Engine: Note off received\n")); ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent); break; + case Event::type_stop_note: + dmsg(5,("Engine: Stop Note received\n")); + ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; case Event::type_control_change: dmsg(5,("Engine: MIDI CC received\n")); ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); @@ -873,6 +897,17 @@ dmsg(5,("Engine: Pitchbend received\n")); ProcessPitchbend(static_cast(itEvent->pEngineChannel), itEvent); break; + case Event::type_note_synth_param: + dmsg(5,("Engine: Note Synth Param received\n")); + ProcessNoteSynthParam(itEvent->pEngineChannel, itEvent); + break; + case Event::type_sysex: + break; // TODO ... + + case Event::type_cancel_release_key: + case Event::type_release_key: + case Event::type_release_note: + break; // noop } } } @@ -975,6 +1010,8 @@ itScriptEvent->cause = *itEvent; itScriptEvent->currentHandler = 0; itScriptEvent->executionSlices = 0; + itScriptEvent->ignoreAllWaitCalls = false; + itScriptEvent->handlerType = pEventHandler->eventHandlerType(); // this is the native representation of the $EVENT_ID script variable itScriptEvent->id = (itEvent->Type == Event::type_note_on) @@ -1242,14 +1279,19 @@ // the script's "init" event handler is only executed // once (when the script is loaded or reloaded) if (pEngineChannel->pScript && pEngineChannel->pScript->handlerInit) { + dmsg(5,("Engine: exec handlerInit %p\n", pEngineChannel->pScript->handlerInit)); RTList::Iterator itScriptEvent = pEngineChannel->pScript->pEvents->allocAppend(); itScriptEvent->cause.pEngineChannel = pEngineChannel; itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit; itScriptEvent->handlers[1] = NULL; + itScriptEvent->currentHandler = 0; + itScriptEvent->executionSlices = 0; + itScriptEvent->ignoreAllWaitCalls = false; + itScriptEvent->handlerType = VM_EVENT_HANDLER_INIT; - VMExecStatus_t res = pScriptVM->exec( + /*VMExecStatus_t res = */ pScriptVM->exec( pEngineChannel->pScript->parserContext, &*itScriptEvent ); @@ -1599,16 +1641,27 @@ MidiKey* pKey = &pChannel->pMIDIKeyInfo[key]; - pChannel->listeners.PreProcessNoteOn(key, vel); + // There are real MIDI note-on events (Event::type_note_on) and + // programmatically spawned notes (Event::type_play_note). We have + // to distinguish between them, since certain processing below + // must only be done on real MIDI note-on events (i.e. for + // correctly updating which MIDI keys are currently pressed down). + const bool isRealMIDINoteOnEvent = itNoteOnEvent->Type == Event::type_note_on; + + if (isRealMIDINoteOnEvent) + pChannel->listeners.PreProcessNoteOn(key, vel); + #if !CONFIG_PROCESS_MUTED_CHANNELS if (pEngineChannel->GetMute()) { // skip if sampler channel is muted - pChannel->listeners.PostProcessNoteOn(key, vel); + if (isRealMIDINoteOnEvent) + pChannel->listeners.PostProcessNoteOn(key, vel); return; } #endif if (!pChannel->pInstrument) { - pChannel->listeners.PostProcessNoteOn(key, vel); + if (isRealMIDINoteOnEvent) + pChannel->listeners.PostProcessNoteOn(key, vel); return; // ignore if no instrument loaded } @@ -1616,7 +1669,7 @@ RTList::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents); // if Solo Mode then kill all already active voices - if (pChannel->SoloMode) { + if (pChannel->SoloMode && isRealMIDINoteOnEvent) { Pool::Iterator itYoungestKey = pChannel->pActiveKeys->last(); if (itYoungestKey) { const int iYoungestKey = *itYoungestKey; @@ -1645,19 +1698,21 @@ pChannel->SoloKey = key; } - pChannel->ProcessKeySwitchChange(key); + if (isRealMIDINoteOnEvent) { + pChannel->ProcessKeySwitchChange(key); - pKey->KeyPressed = true; // the MIDI key was now pressed down - pChannel->KeyDown[key] = true; // just used as built-in %KEY_DOWN script variable - pKey->Velocity = itNoteOnEventOnKeyList->Param.Note.Velocity; - pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length + pKey->KeyPressed = true; // the MIDI key was now pressed down + pChannel->KeyDown[key] = true; // just used as built-in %KEY_DOWN script variable + pKey->Velocity = itNoteOnEventOnKeyList->Param.Note.Velocity; + pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length + } // cancel release process of voices on this key if needed - if (pKey->Active && !pChannel->SustainPedal) { + if (pKey->Active && !pChannel->SustainPedal && isRealMIDINoteOnEvent) { RTList::Iterator itCancelReleaseEvent = pKey->pEvents->allocAppend(); if (itCancelReleaseEvent) { *itCancelReleaseEvent = *itNoteOnEventOnKeyList; // copy event - itCancelReleaseEvent->Type = Event::type_cancel_release; // transform event type + itCancelReleaseEvent->Type = Event::type_cancel_release_key; // transform event type } else dmsg(1,("Event pool emtpy!\n")); } @@ -1668,12 +1723,17 @@ if (!pKey->Active && !pKey->VoiceTheftsQueued) pKey->pEvents->free(itNoteOnEventOnKeyList); - if (!pChannel->SoloMode || pChannel->PortamentoPos < 0.0f) pChannel->PortamentoPos = (float) key; + if (isRealMIDINoteOnEvent && (!pChannel->SoloMode || pChannel->PortamentoPos < 0.0f)) + pChannel->PortamentoPos = (float) key; + + //NOTE: Hmm, I guess its a matter of taste whether round robin should be advanced only on real MIDI note-on events, isn't it? if (pKey->pRoundRobinIndex) { (*pKey->pRoundRobinIndex)++; // counter specific for the key or region pChannel->RoundRobinIndex++; // common counter for the channel } - pChannel->listeners.PostProcessNoteOn(key, vel); + + if (isRealMIDINoteOnEvent) + pChannel->listeners.PostProcessNoteOn(key, vel); } /** @@ -1711,91 +1771,111 @@ MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey]; - pChannel->listeners.PreProcessNoteOff(iKey, vel); + // There are real MIDI note-off events (Event::type_note_off) and + // programmatically spawned notes (Event::type_stop_note). We have + // to distinguish between them, since certain processing below + // must only be done on real MIDI note-off events (i.e. for + // correctly updating which MIDI keys are currently pressed down), + // plus a stop-note event just releases voices of one particular + // note, whereas a note-off event releases all voices on a + // particular MIDI key instead. + const bool isRealMIDINoteOffEvent = itNoteOffEvent->Type == Event::type_note_off; + + if (isRealMIDINoteOffEvent) + pChannel->listeners.PreProcessNoteOff(iKey, vel); #if !CONFIG_PROCESS_MUTED_CHANNELS if (pEngineChannel->GetMute()) { // skip if sampler channel is muted - pChannel->listeners.PostProcessNoteOff(iKey, vel); + if (isRealMIDINoteOffEvent) + pChannel->listeners.PostProcessNoteOff(iKey, vel); return; } #endif - pKey->KeyPressed = false; // the MIDI key was now released - pChannel->KeyDown[iKey] = false; // just used as built-in %KEY_DOWN script variable + if (isRealMIDINoteOffEvent) { + pKey->KeyPressed = false; // the MIDI key was now released + pChannel->KeyDown[iKey] = false; // just used as built-in %KEY_DOWN script variable + } // move event to the key's own event list RTList::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents); - bool bShouldRelease = pKey->Active && pChannel->ShouldReleaseVoice(itNoteOffEventOnKeyList->Param.Note.Key); + if (isRealMIDINoteOffEvent) { + bool bShouldRelease = pKey->Active && pChannel->ShouldReleaseVoice(itNoteOffEventOnKeyList->Param.Note.Key); - // in case Solo Mode is enabled, kill all voices on this key and respawn a voice on the highest pressed key (if any) - if (pChannel->SoloMode && pChannel->pInstrument) { //TODO: this feels like too much code just for handling solo mode :P - bool bOtherKeysPressed = false; - if (iKey == pChannel->SoloKey) { - pChannel->SoloKey = -1; - // if there's still a key pressed down, respawn a voice (group) on the highest key - for (int i = 127; i > 0; i--) { - MidiKey* pOtherKey = &pChannel->pMIDIKeyInfo[i]; - if (pOtherKey->KeyPressed) { - bOtherKeysPressed = true; - // make the other key the new 'currently active solo key' - pChannel->SoloKey = i; - // get final portamento position of currently active voice - if (pChannel->PortamentoMode) { - NoteIterator itNote = pKey->pActiveNotes->first(); - VoiceIterator itVoice = itNote->pActiveVoices->first(); - if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList); - } - // create a pseudo note on event - RTList::Iterator itPseudoNoteOnEvent = pOtherKey->pEvents->allocAppend(); - if (itPseudoNoteOnEvent) { - // copy event - *itPseudoNoteOnEvent = *itNoteOffEventOnKeyList; - // transform event to a note on event - itPseudoNoteOnEvent->Type = Event::type_note_on; - itPseudoNoteOnEvent->Param.Note.Key = i; - itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity; - // assign a new note to this note-on event - if (LaunchNewNote(pChannel, &*itPseudoNoteOnEvent)) { - // allocate and trigger new voice(s) for the other key - TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false); + // in case Solo Mode is enabled, kill all voices on this key and respawn a voice on the highest pressed key (if any) + if (pChannel->SoloMode && pChannel->pInstrument) { //TODO: this feels like too much code just for handling solo mode :P + bool bOtherKeysPressed = false; + if (iKey == pChannel->SoloKey) { + pChannel->SoloKey = -1; + // if there's still a key pressed down, respawn a voice (group) on the highest key + for (int i = 127; i > 0; i--) { + MidiKey* pOtherKey = &pChannel->pMIDIKeyInfo[i]; + if (pOtherKey->KeyPressed) { + bOtherKeysPressed = true; + // make the other key the new 'currently active solo key' + pChannel->SoloKey = i; + // get final portamento position of currently active voice + if (pChannel->PortamentoMode) { + NoteIterator itNote = pKey->pActiveNotes->first(); + VoiceIterator itVoice = itNote->pActiveVoices->first(); + if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList); } - // if neither a voice was spawned or postponed then remove note on event from key again - if (!pOtherKey->Active && !pOtherKey->VoiceTheftsQueued) - pOtherKey->pEvents->free(itPseudoNoteOnEvent); + // create a pseudo note on event + RTList::Iterator itPseudoNoteOnEvent = pOtherKey->pEvents->allocAppend(); + if (itPseudoNoteOnEvent) { + // copy event + *itPseudoNoteOnEvent = *itNoteOffEventOnKeyList; + // transform event to a note on event + itPseudoNoteOnEvent->Type = Event::type_note_on; //FIXME: should probably use Event::type_play_note instead (to avoid i.e. hanging notes) + itPseudoNoteOnEvent->Param.Note.Key = i; + itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity; + // assign a new note to this note-on event + if (LaunchNewNote(pChannel, &*itPseudoNoteOnEvent)) { + // allocate and trigger new voice(s) for the other key + TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false); + } + // if neither a voice was spawned or postponed then remove note on event from key again + if (!pOtherKey->Active && !pOtherKey->VoiceTheftsQueued) + pOtherKey->pEvents->free(itPseudoNoteOnEvent); - } else dmsg(1,("Could not respawn voice, no free event left\n")); - break; // done + } else dmsg(1,("Could not respawn voice, no free event left\n")); + break; // done + } } } - } - if (bOtherKeysPressed) { - if (pKey->Active) { // kill all voices on this key - bShouldRelease = false; // no need to release, as we kill it here - for (NoteIterator itNote = pKey->pActiveNotes->first(); itNote; ++itNote) { - VoiceIterator itVoiceToBeKilled = itNote->pActiveVoices->first(); - VoiceIterator end = itNote->pActiveVoices->end(); - for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) - itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); + if (bOtherKeysPressed) { + if (pKey->Active) { // kill all voices on this key + bShouldRelease = false; // no need to release, as we kill it here + for (NoteIterator itNote = pKey->pActiveNotes->first(); itNote; ++itNote) { + VoiceIterator itVoiceToBeKilled = itNote->pActiveVoices->first(); + VoiceIterator end = itNote->pActiveVoices->end(); + for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { + if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) + itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); + } } } - } - } else pChannel->PortamentoPos = -1.0f; - } + } else pChannel->PortamentoPos = -1.0f; + } - // if no solo mode (the usual case) or if solo mode and no other key pressed, then release voices on this key if needed - if (bShouldRelease) { - itNoteOffEventOnKeyList->Type = Event::type_release; // transform event type - - // spawn release triggered voice(s) if needed - if (pKey->ReleaseTrigger && pChannel->pInstrument) { - // assign a new note to this release event - if (LaunchNewNote(pChannel, &*itNoteOffEventOnKeyList)) { - // allocate and trigger new release voice(s) - TriggerReleaseVoices(pChannel, itNoteOffEventOnKeyList); - } - pKey->ReleaseTrigger = false; + // if no solo mode (the usual case) or if solo mode and no other key pressed, then release voices on this key if needed + if (bShouldRelease) { + itNoteOffEventOnKeyList->Type = Event::type_release_key; // transform event type + // spawn release triggered voice(s) if needed + ProcessReleaseTrigger(pChannel, itNoteOffEventOnKeyList, pKey); + } + } else if (itNoteOffEventOnKeyList->Type == Event::type_stop_note) { + // This programmatically caused event is caused by a call to + // the built-in instrument script function note_off(). In + // contrast to a real MIDI note-off event the stop-note + // event just intends to release voices of one particular note. + NoteBase* pNote = pChannel->pEngine->NoteByID( itNoteOffEventOnKeyList->Param.Note.ID ); + if (pNote) { // the requested note is still alive ... + itNoteOffEventOnKeyList->Type = Event::type_release_note; // transform event type + } else { // note is dead and gone .. + pKey->pEvents->free(itNoteOffEventOnKeyList); // remove stop-note event from key again + return; // prevent event to be removed a 2nd time below } } @@ -1803,7 +1883,119 @@ if (!pKey->Active && !pKey->VoiceTheftsQueued) pKey->pEvents->free(itNoteOffEventOnKeyList); - pChannel->listeners.PostProcessNoteOff(iKey, vel); + if (isRealMIDINoteOffEvent) + pChannel->listeners.PostProcessNoteOff(iKey, vel); + } + + /** + * Called on sustain pedal up events to check and if required, + * launch release trigger voices on the respective active key. + * + * @param pEngineChannel - engine channel on which this event occurred on + * @param itEvent - release trigger event (contains note number) + */ + virtual void ProcessReleaseTrigger(EngineChannel* pEngineChannel, RTList::Iterator& itEvent) OVERRIDE { + EngineChannelBase* pChannel = static_cast*>(pEngineChannel); + + const int iKey = itEvent->Param.Note.Key; + if (iKey < 0 || iKey > 127) return; // ignore event, key outside allowed key range + + MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey]; + + ProcessReleaseTrigger(pChannel, itEvent, pKey); + } + + /** + * Called on note-off and sustain pedal up events to check and if + * required, launch release trigger voices on the respective active + * key. + * + * @param pEngineChannel - engine channel on which this event occurred on + * @param itEvent - note off event / release trigger event + * @param pKey - key on which the release trigger voices shall be spawned + */ + inline void ProcessReleaseTrigger(EngineChannelBase* pChannel, RTList::Iterator& itEvent, MidiKey* pKey) { + // spawn release triggered voice(s) if needed + if (pKey->ReleaseTrigger && pChannel->pInstrument) { + // assign a new note to this release event + if (LaunchNewNote(pChannel, &*itEvent)) { + // allocate and trigger new release voice(s) + TriggerReleaseVoices(pChannel, itEvent); + } + pKey->ReleaseTrigger = false; + } + } + + /** + * Called on note synthesis parameter change events. These are + * internal events caused by calling built-in real-time instrument + * script functions like change_vol(), change_pitch(), etc. + * + * This method performs two tasks: + * + * - It converts the event's relative values changes (Deltas) to + * the respective final new synthesis parameter value (AbsValue), + * for that particular moment of the event that is. + * + * - It moves the individual events to the Note's own event list + * (or actually to the event list of the MIDI key), so that + * voices can process those events sample accurately. + * + * @param pEngineChannel - engine channel on which this event occurred on + * @param itEvent - note synthesis parameter change event + */ + virtual void ProcessNoteSynthParam(EngineChannel* pEngineChannel, RTList::Iterator& itEvent) { + EngineChannelBase* pChannel = static_cast*>(pEngineChannel); + + NoteBase* pNote = pChannel->pEngine->NoteByID( itEvent->Param.NoteSynthParam.NoteID ); + if (!pNote || pNote->hostKey < 0 || pNote->hostKey >= 128) return; + + const bool& relative = itEvent->Param.NoteSynthParam.Relative; + + switch (itEvent->Param.NoteSynthParam.Type) { + case Event::synth_param_volume: + if (relative) + pNote->Override.Volume *= itEvent->Param.NoteSynthParam.Delta; + else + pNote->Override.Volume = itEvent->Param.NoteSynthParam.Delta; + itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Volume; + break; + case Event::synth_param_pitch: + if (relative) + pNote->Override.Pitch *= itEvent->Param.NoteSynthParam.Delta; + else + pNote->Override.Pitch = itEvent->Param.NoteSynthParam.Delta; + itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pitch; + break; + case Event::synth_param_pan: + if (relative) { + pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, itEvent->Param.NoteSynthParam.Delta, ++pNote->Override.PanSources); + } else { + pNote->Override.Pan = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.PanSources = 1; // only relevant on subsequent change_pan() instrument script calls on same note with 'relative' argument being set + } + itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pan; + break; + case Event::synth_param_cutoff: + pNote->Override.Cutoff = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_resonance: + pNote->Override.Resonance = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_attack: + pNote->Override.Attack = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_decay: + pNote->Override.Decay = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_release: + pNote->Override.Release = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + } + + // move note parameter event to its MIDI key + MidiKey* pKey = &pChannel->pMIDIKeyInfo[pNote->hostKey]; + itEvent.moveToEndOf(pKey->pEvents); } /**