--- linuxsampler/trunk/src/engines/EngineBase.h 2013/03/07 19:23:24 2434 +++ linuxsampler/trunk/src/engines/EngineBase.h 2014/06/18 00:14:57 2645 @@ -143,6 +143,10 @@ } } } + + // In case scale tuning has been changed, recalculate pitch for + // all active voices. + ProcessScaleTuningChange(); // reset internal voice counter (just for statistic of active voices) ActiveVoiceCountTemp = 0; @@ -627,7 +631,46 @@ AbstractEngineChannel* pChannel = static_cast(pEngineChannel); pChannel->ImportEvents(Samples); - // process events + // 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->pScript && pChannel->pScript->bHasValidScript) { + // resume any suspended script executions still hanging + // around of previous audio fragment cycles + for (RTList::Iterator itEvent = pChannel->pScript->pEvents->first(), + end = pChannel->pScript->pEvents->end(); itEvent != end; ++itEvent) + { + ResumeScriptEvent(pChannel, itEvent); //TODO: implement support for actual suspension time (i.e. passed to a script's wait() function call) + } + + // spawn new script executions for the new MIDI events of + // this audio fragment cycle + for (RTList::Iterator itEvent = pChannel->pEvents->first(), + end = pChannel->pEvents->end(); itEvent != end; ++itEvent) + { + switch (itEvent->Type) { + case Event::type_note_on: + if (pChannel->pScript->handlerNote) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerNote); + break; + case Event::type_note_off: + 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->pScript->handlerController) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerController); + break; + case Event::type_note_pressure: + //TODO: ... + break; + } + } + } + + // now process all events regularly { RTList::Iterator itEvent = pChannel->pEvents->first(); RTList::Iterator end = pChannel->pEvents->end(); @@ -645,6 +688,14 @@ dmsg(5,("Engine: MIDI CC received\n")); ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); break; + case Event::type_channel_pressure: + dmsg(5,("Engine: MIDI Chan. Pressure received\n")); + ProcessChannelPressure((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; + case Event::type_note_pressure: + dmsg(5,("Engine: MIDI Note Pressure received\n")); + ProcessPolyphonicKeyPressure((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; case Event::type_pitchbend: dmsg(5,("Engine: Pitchbend received\n")); ProcessPitchbend(static_cast(itEvent->pEngineChannel), itEvent); @@ -661,6 +712,140 @@ pLastStolenChannel = NULL; } + /** @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 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) { + 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 + ); + } + } + + 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; + itScriptEvent->handlers[i++] = pEventHandler; // actual event handler (i.e. note, controller) + itScriptEvent->handlers[i] = NULL; // NULL termination of list + + // initialize/reset other members + itScriptEvent->cause = *itEvent; + itScriptEvent->id = pEventPool->getID(itEvent); + itScriptEvent->currentHandler = 0; + itScriptEvent->executionSlices = 0; + + // run script handler(s) + VMExecStatus_t res = pScriptVM->exec( + 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 + if (!(res & VM_EXEC_SUSPENDED)) { // 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. + * + * Will be called to resume execution of a real-time instrument + * script event which has been suspended in a previous audio + * fragment cycle. + * + * Script execution might be suspended for various reasons. Usually + * a script will be suspended if the script called the built-in + * "wait()" function, but it might also be suspended automatically + * if the script took too much execution time in an audio fragment + * cycle. So in the latter case automatic suspension is performed in + * order to avoid harm for the sampler's overall real-time + * requirements. + * + * @param pChannel - engine channel this script is running for + * @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->pScript->parserContext, &*itScriptEvent + ); + + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be continued on the next audio cycle + if (!(res & VM_EXEC_SUSPENDED)) { // 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); + } + } + } + /** * Will be called by LaunchVoice() method in case there are no free * voices left. This method will select and kill one old voice for @@ -793,9 +978,27 @@ dmsg(5,("Engine: instrument change command received\n")); cmd.bChangeInstrument = false; pEngineChannel->pInstrument = cmd.pInstrument; + pEngineChannel->pScript = cmd.pScript; 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); + } } } @@ -1173,6 +1376,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 @@ -1249,6 +1453,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); @@ -1452,6 +1657,22 @@ return -1; } + + /** + * Checks whether scale tuning setting has been changed since last + * time this method was called, if yes, it recalculates the pitch + * for all active voices. + */ + void ProcessScaleTuningChange() { + const bool changed = ScaleTuningChanged.readAndReset(); + if (!changed) return; + + for (int i = 0; i < engineChannels.size(); i++) { + EngineChannelBase* channel = + static_cast*>(engineChannels[i]); + channel->OnScaleTuningChanged(); + } + } private: Pool* pVoicePool; ///< Contains all voices that can be activated.