--- linuxsampler/trunk/src/engines/EngineBase.h 2016/04/19 14:07:53 2879 +++ linuxsampler/trunk/src/engines/EngineBase.h 2016/07/17 17:54:04 2962 @@ -689,6 +689,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 @@ -771,8 +774,12 @@ // // FIXME: it would probably be better to just schedule newly spawned script executions here and then execute them altogether with already suspended ones all at once in order of all their scheduled timing for (RTList::Iterator itEvent = pChannel->pEvents->first(), - end = pChannel->pEvents->end(); itEvent != end; ++itEvent) + end = pChannel->pEvents->end(); itEvent != end; ) { + //HACK: avoids iterator invalidation which might happen below since an instrument script might drop an event by direct raw pointer access (it would be considerable to extend the Iterator class to detect and circumvent this case by checking the "reincarnation" member variable). + RTList::Iterator itNext = itEvent; + ++itNext; + switch (itEvent->Type) { case Event::type_note_on: if (pChannel->pScript->handlerNote) @@ -792,6 +799,9 @@ //TODO: ... break; } + + // see HACK comment above + itEvent = itNext; } // this has to be run again, since the newly spawned scripts @@ -846,10 +856,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); @@ -866,6 +884,10 @@ 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; } } } @@ -968,6 +990,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) @@ -1592,16 +1616,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 } @@ -1609,7 +1644,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; @@ -1638,19 +1673,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")); } @@ -1661,12 +1698,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); } /** @@ -1704,91 +1746,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 } } @@ -1796,7 +1858,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); } /**