--- linuxsampler/trunk/src/engines/EngineBase.h 2014/06/06 12:38:54 2598 +++ linuxsampler/trunk/src/engines/EngineBase.h 2016/04/10 18:22:23 2871 @@ -4,7 +4,8 @@ * * * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * - * Copyright (C) 2009-2013 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2012-2016 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 * @@ -432,14 +433,14 @@ * @param pRegion - region the engine shall stop using */ virtual void Suspend(RR* pRegion) { - dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion)); + dmsg(2,("EngineBase: Suspending Region %p ...\n",(void*)pRegion)); { LockGuard lock(SuspendedRegionsMutex); SuspensionChangeOngoing.Set(true); pPendingRegionSuspension = pRegion; SuspensionChangeOngoing.WaitAndUnlockIf(true); } - dmsg(2,("EngineBase: Region %x suspended.",pRegion)); + dmsg(2,("EngineBase: Region %p suspended.",(void*)pRegion)); } /** @@ -449,14 +450,14 @@ * @param pRegion - region the engine shall be allowed to use again */ virtual void Resume(RR* pRegion) { - dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion)); + dmsg(2,("EngineBase: Resuming Region %p ...\n",(void*)pRegion)); { LockGuard lock(SuspendedRegionsMutex); SuspensionChangeOngoing.Set(true); pPendingRegionResumption = pRegion; SuspensionChangeOngoing.WaitAndUnlockIf(true); } - dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); + dmsg(2,("EngineBase: Region %p resumed.\n",(void*)pRegion)); } virtual void ResetSuspendedRegions() { @@ -634,40 +635,82 @@ // if a valid real-time instrument script is loaded, pre-process // the event list by running the script now, since the script // might filter events or add new ones for this cycle - if (pChannel->script.bHasValidScript) { - // resume any suspended script executions still hanging - // around of previous audio fragment cycles - for (RTList::Iterator itEvent = pChannel->pScriptEvents->first(), - end = pChannel->pScriptEvents->end(); itEvent != end; ++itEvent) - { - ResumeScriptEvent(pChannel, itEvent); //TODO: implement support for actual suspension time (i.e. passed to a script's wait() function call) - } + if (pChannel->pScript) { + const sched_time_t fragmentEndTime = pEventGenerator->schedTimeAtCurrentFragmentEnd(); + + // resume suspended script executions been scheduled for + // this audio fragment cycle (which were suspended in a + // previous audio fragment cycle) + ProcessSuspendedScriptEvents(pChannel, fragmentEndTime); // spawn new script executions for the new MIDI events of // this audio fragment cycle + // + // 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) { switch (itEvent->Type) { case Event::type_note_on: - if (pChannel->script.handlerNote) - ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerNote); + if (pChannel->pScript->handlerNote) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerNote); break; case Event::type_note_off: - if (pChannel->script.handlerRelease) - ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerRelease); + if (pChannel->pScript->handlerRelease) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerRelease); break; case Event::type_control_change: case Event::type_channel_pressure: case Event::type_pitchbend: - if (pChannel->script.handlerController) - ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerController); + if (pChannel->pScript->handlerController) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerController); break; case Event::type_note_pressure: //TODO: ... break; } } + + // this has to be run again, since the newly spawned scripts + // above may have cause suspended scripts that must be + // resumed within this same audio fragment cycle + // + // FIXME: see FIXME comment above + ProcessSuspendedScriptEvents(pChannel, fragmentEndTime); + } + + // if there are any delayed events scheduled for the current + // audio fragment cycle, then move and sort them into the main + // event list + if (!pChannel->delayedEvents.queue.isEmpty()) { + dmsg(5,("Engine: There are delayed MIDI events (total queue size: %d) ...\n", pChannel->delayedEvents.queue.size())); + const sched_time_t fragmentEndTime = pEventGenerator->schedTimeAtCurrentFragmentEnd(); + RTList::Iterator itEvent = pChannel->pEvents->first(); + while (true) { + RTList::Iterator itDelayedEventNode = + pEventGenerator->popNextScheduledEvent( + pChannel->delayedEvents.queue, + pChannel->delayedEvents.schedulerNodes, + fragmentEndTime + ); + if (!itDelayedEventNode) break; + // get the actual delayed event object and free the used scheduler node + RTList::Iterator itDelayedEvent = itDelayedEventNode->itEvent; + pChannel->delayedEvents.schedulerNodes.free(itDelayedEventNode); + if (!itDelayedEvent) { // should never happen, but just to be sure ... + dmsg(1,("Engine: Oops, invalid delayed event!\n")); + continue; + } + // skip all events on main event list which have a time + // before (or equal to) the delayed event to be inserted + for (; itEvent && itEvent->FragmentPos() <= itDelayedEvent->FragmentPos(); + ++itEvent); + // now move delayed event from delayedEvents.pList to + // the current position on the main event list + itEvent = itDelayedEvent.moveBefore(itEvent); + dmsg(5,("Engine: Inserted event of type %d into main event list (queue size: %d).\n", itEvent->Type, pChannel->delayedEvents.queue.size())); + } + dmsg(5,("Engine: End of delayed events (total queue size: %d).\n", pChannel->delayedEvents.queue.size())); } // now process all events regularly @@ -712,26 +755,87 @@ pLastStolenChannel = NULL; } + /** + * Run all suspended script execution instances which are scheduled + * to be resumed for the current audio fragment cycle. + * + * @param pChannel - engine channel on which suspended events occurred + */ + void ProcessSuspendedScriptEvents(AbstractEngineChannel* pChannel, const sched_time_t fragmentEndTime) { + while (true) { + RTList::Iterator itEvent = + pEventGenerator->popNextScheduledScriptEvent( + pChannel->pScript->suspendedEvents, + *pChannel->pScript->pEvents, fragmentEndTime + ); + if (!itEvent) break; + ResumeScriptEvent(pChannel, itEvent); + } + } + /** @brief Call instrument script's event handler for this event. * * Causes a new execution instance of the currently loaded real-time * instrument script's event handler (callback) to be spawned for * the given MIDI event. * - * @param pChannel - engine channel on which the MIDI event occured + * @param pChannel - engine channel on which the MIDI event occurred * @param itEvent - MIDI event that causes this new script execution * @param pEventHandler - script's event handler to be executed */ void ProcessEventByScript(AbstractEngineChannel* pChannel, RTList::Iterator& itEvent, VMEventHandler* pEventHandler) { - RTList::Iterator itScriptEvent = - pChannel->pScriptEvents->allocAppend(); + const int key = itEvent->Param.Note.Key; // even if this is not a note on/off event, accessing it does not mean any harm + // check if polyphonic data is passed from "note" to "release" + // script event handlers + if (pEventHandler == pChannel->pScript->handlerRelease && + pChannel->pScript->handlerNote && + pChannel->pScript->handlerNote->isPolyphonic() && + pChannel->pScript->handlerRelease->isPolyphonic() && + !pChannel->pScript->pKeyEvents[key]->isEmpty()) + { + // polyphonic variable data is used/passed from "note" to + // "release" script callback, so we have to recycle the + // original "note on" script event(s) + RTList::Iterator it = pChannel->pScript->pKeyEvents[key]->first(); + RTList::Iterator end = pChannel->pScript->pKeyEvents[key]->end(); + for (; it != end; ++it) { + ProcessScriptEvent( + pChannel, itEvent, pEventHandler, it + ); + } + } else { + // no polyphonic data is used/passed from "note" to + // "release" script callback, so just use a new fresh + // script event object + RTList::Iterator itScriptEvent = + pChannel->pScript->pEvents->allocAppend(); + ProcessScriptEvent( + pChannel, itEvent, pEventHandler, itScriptEvent + ); + } + } - if (!itScriptEvent) return; // no free script event left for execution + /** @brief Spawn new execution instance of an instrument script handler. + * + * Will be called to initiate a new execution of a real-time + * instrument script event right from the start of the script's + * respective handler. If script execution did not complete after + * calling this method, the respective script exeuction is then + * suspended and a call to ResumeScriptEvent() will be used next + * time to continue its execution. + * + * @param pChannel - engine channel this script is running for + * @param itEvent - event which caused execution of this script + * event handler + * @param pEventHandler - VM representation of event handler to be + * executed + * @param itScriptEvent - script event that shall be processed + */ + void ProcessScriptEvent(AbstractEngineChannel* pChannel, RTList::Iterator& itEvent, VMEventHandler* pEventHandler, RTList::Iterator& itScriptEvent) { + if (!itScriptEvent) return; // not a valid script event (i.e. because no free script event was left in the script event pool) // fill the list of script handlers to be executed by this event int i = 0; - if (pChannel->script.handlerInit) - itScriptEvent->handlers[i++] = pChannel->script.handlerInit; itScriptEvent->handlers[i++] = pEventHandler; // actual event handler (i.e. note, controller) itScriptEvent->handlers[i] = NULL; // NULL termination of list @@ -743,15 +847,40 @@ // run script handler(s) VMExecStatus_t res = pScriptVM->exec( - pChannel->script.parserContext, &*itScriptEvent + pChannel->pScript->parserContext, &*itScriptEvent ); - // in case the script was suspended, keep it on the allocated - // ScriptEvent list to be continued on the next audio cycle, - // otherwise if execution has been finished, free it for a new - // future script event to be triggered from start - if (!(res & VM_EXEC_SUSPENDED)) - pChannel->pScriptEvents->free(itScriptEvent); + // was the script suspended? + if (res & VM_EXEC_SUSPENDED) { // script was suspended ... + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be resume at the scheduled time in future, + // additionally insert it into a sorted time queue + pEventGenerator->scheduleAheadMicroSec( + pChannel->pScript->suspendedEvents, // scheduler queue + *itScriptEvent, // script event + itScriptEvent->cause.FragmentPos(), // current time of script event (basis for its next execution) + itScriptEvent->execCtx->suspensionTimeMicroseconds() // how long shall it be suspended + ); + } else { // script execution has finished without 'suspended' status ... + // if "polyphonic" variable data is passed from script's + // "note" event handler to its "release" event handler, then + // the script event must be kept and recycled for the later + // occuring "release" script event ... + if (pEventHandler == pChannel->pScript->handlerNote && + pChannel->pScript->handlerRelease && + pChannel->pScript->handlerNote->isPolyphonic() && + pChannel->pScript->handlerRelease->isPolyphonic()) + { + const int key = itEvent->Param.Note.Key; + itScriptEvent.moveToEndOf(pChannel->pScript->pKeyEvents[key & 127]); + } else { + // ... otherwise if no polyphonic data is passed and + // script's execution has finished without suspension + // status, then free the script event for a new future + // script event to be triggered from start + pChannel->pScript->pEvents->free(itScriptEvent); + } + } } /** @brief Resume execution of instrument script. @@ -772,16 +901,44 @@ * @param itScriptEvent - script execution that shall be resumed */ void ResumeScriptEvent(AbstractEngineChannel* pChannel, RTList::Iterator& itScriptEvent) { + VMEventHandler* handler = itScriptEvent->handlers[itScriptEvent->currentHandler]; + // run script VMExecStatus_t res = pScriptVM->exec( - pChannel->script.parserContext, &*itScriptEvent + pChannel->pScript->parserContext, &*itScriptEvent ); - // in case the script was again suspended, keep it on the allocated - // ScriptEvent list to be continued on the next audio cycle, - // otherwise if execution has been finished, free it for a new - // future script event to be triggered from start - if (!(res & VM_EXEC_SUSPENDED)) - pChannel->pScriptEvents->free(itScriptEvent); + + // was the script suspended? + if (res & VM_EXEC_SUSPENDED) { + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be resume at the scheduled time in future, + // additionally insert it into a sorted time queue + pEventGenerator->scheduleAheadMicroSec( + pChannel->pScript->suspendedEvents, // scheduler queue + *itScriptEvent, // script event + itScriptEvent->cause.FragmentPos(), // current time of script event (basis for its next execution) + itScriptEvent->execCtx->suspensionTimeMicroseconds() // how long shall it be suspended + ); + } else { // script execution has finished without 'suspended' status ... + // if "polyphonic" variable data is passed from script's + // "note" event handler to its "release" event handler, then + // the script event must be kept and recycled for the later + // occuring "release" script event ... + if (handler && handler == pChannel->pScript->handlerNote && + pChannel->pScript->handlerRelease && + pChannel->pScript->handlerNote->isPolyphonic() && + pChannel->pScript->handlerRelease->isPolyphonic()) + { + const int key = itScriptEvent->cause.Param.Note.Key; + itScriptEvent.moveToEndOf(pChannel->pScript->pKeyEvents[key & 127]); + } else { + // ... otherwise if no polyphonic data is passed and + // script's execution has finished without suspension + // status, then free the script event for a new future + // script event to be triggered from start + pChannel->pScript->pEvents->free(itScriptEvent); + } + } } /** @@ -790,7 +947,7 @@ * voice stealing and postpone the note-on event until the selected * voice actually died. * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event * @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing */ @@ -916,9 +1073,28 @@ dmsg(5,("Engine: instrument change command received\n")); cmd.bChangeInstrument = false; pEngineChannel->pInstrument = cmd.pInstrument; + pEngineChannel->pScript = + cmd.pScript->bHasValidScript ? cmd.pScript : NULL; instrumentChanged = true; pEngineChannel->MarkAllActiveVoicesAsOrphans(); + + // the script's "init" event handler is only executed + // once (when the script is loaded or reloaded) + if (pEngineChannel->pScript && pEngineChannel->pScript->handlerInit) { + RTList::Iterator itScriptEvent = + pEngineChannel->pScript->pEvents->allocAppend(); + + itScriptEvent->cause.pEngineChannel = pEngineChannel; + itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit; + itScriptEvent->handlers[1] = NULL; + + VMExecStatus_t res = pScriptVM->exec( + pEngineChannel->pScript->parserContext, &*itScriptEvent + ); + + pEngineChannel->pScript->pEvents->free(itScriptEvent); + } } } @@ -1009,7 +1185,8 @@ pChannel->FreeAllInactiveKyes(); // empty the engine channel's own event lists - pChannel->ClearEventLists(); + // (only events of the current audio fragment cycle) + pChannel->ClearEventListsOfCurrentFragment(); } /** @@ -1074,7 +1251,7 @@ case 0x1d: { // reverb send of note (Roland GS NRPN) const uint note = NrpnCtrlLSB; const float reverb = float(itControlChangeEvent->Param.CC.Value) / 127.0f; - dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%d).\n", note, reverb)); + dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%f).\n", note, reverb)); if (note < 128) pChannel->pMIDIKeyInfo[note].ReverbSend = reverb; break; @@ -1082,7 +1259,7 @@ case 0x1e: { // chorus send of note (Roland GS NRPN) const uint note = NrpnCtrlLSB; const float chorus = float(itControlChangeEvent->Param.CC.Value) / 127.0f; - dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%d).\n", note, chorus)); + dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%f).\n", note, chorus)); if (note < 128) pChannel->pMIDIKeyInfo[note].ChorusSend = chorus; break; @@ -1235,7 +1412,7 @@ /** * Assigns and triggers a new voice for the respective MIDI key. * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event */ virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { @@ -1296,6 +1473,7 @@ 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 @@ -1346,7 +1524,7 @@ * sustain pedal will be released or voice turned inactive by itself (e.g. * due to completion of sample playback). * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOffEvent - key, velocity and time stamp of the event */ virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOffEvent) { @@ -1372,6 +1550,7 @@ #endif 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); @@ -1473,6 +1652,13 @@ } pVoicePool->clear(); + // reset all engine channels + for (int i = 0; i < engineChannels.size(); i++) { + AbstractEngineChannel* pEngineChannel = + static_cast(engineChannels[i]); + pEngineChannel->ResetInternal(false/*don't reset engine*/); + } + // reset disk thread if (pDiskThread) pDiskThread->Reset(); @@ -1501,7 +1687,7 @@ * called by the ProcessNoteOn() method and by the voices itself * (e.g. to spawn further voices on the same key for layered sounds). * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event * @param iLayer - layer index for the new voice (optional - only * in case of layered sounds of course)