--- linuxsampler/trunk/src/engines/EngineBase.h 2013/03/07 19:23:24 2434 +++ linuxsampler/trunk/src/engines/EngineBase.h 2014/06/11 11:39:44 2618 @@ -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,76 @@ 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) { + RTList::Iterator itScriptEvent = + pChannel->pScript->pEvents->allocAppend(); + + if (!itScriptEvent) return; // no free script event left for execution + + // 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, + // 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->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) { + // run script + VMExecStatus_t res = pScriptVM->exec( + 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->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 +914,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 +1312,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 +1389,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 +1593,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.