--- linuxsampler/trunk/src/engines/EngineBase.h 2014/06/11 11:39:44 2618 +++ linuxsampler/trunk/src/engines/EngineBase.h 2016/04/19 14:07:53 2879 @@ -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 * @@ -32,6 +33,9 @@ #include "InstrumentManager.h" #include "../common/global_private.h" +// a bit headroom over CONFIG_MAX_VOICES to avoid minor complications i.e. under voice stealing conditions +#define MAX_NOTES_HEADROOM 3 +#define GLOBAL_MAX_NOTES (GLOBAL_MAX_VOICES * MAX_NOTES_HEADROOM) namespace LinuxSampler { @@ -45,27 +49,41 @@ class IM /* Instrument Manager */, class I /* Instrument */ > - class EngineBase: public AbstractEngine, public RegionPools, public VoicePool { + class EngineBase: public AbstractEngine, public RegionPools, public NotePool { public: + typedef typename RTList< Note >::Iterator NoteIterator; typedef typename RTList::Iterator VoiceIterator; typedef typename Pool::Iterator PoolVoiceIterator; typedef typename RTList::Iterator RootRegionIterator; typedef typename MidiKeyboardManager::MidiKey MidiKey; - EngineBase() : SuspendedRegions(128) { + EngineBase() : SuspendedRegions(128), noteIDPool(GLOBAL_MAX_NOTES) { pDiskThread = NULL; + pNotePool = new Pool< Note >(GLOBAL_MAX_NOTES); + pNotePool->setPoolElementIDsReservedBits(INSTR_SCRIPT_EVENT_ID_RESERVED_BITS); pVoicePool = new Pool(GLOBAL_MAX_VOICES); pRegionPool[0] = new Pool(GLOBAL_MAX_VOICES); pRegionPool[1] = new Pool(GLOBAL_MAX_VOICES); pVoiceStealingQueue = new RTList(pEventPool); iMaxDiskStreams = GLOBAL_MAX_STREAMS; - for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { + // init all Voice objects in voice pool + for (VoiceIterator iterVoice = pVoicePool->allocAppend(); + iterVoice; iterVoice = pVoicePool->allocAppend()) + { iterVoice->SetEngine(this); } pVoicePool->clear(); + // init all Note objects in note pool + for (NoteIterator itNote = pNotePool->allocAppend(); itNote; + itNote = pNotePool->allocAppend()) + { + itNote->init(pVoicePool, ¬eIDPool); + } + pNotePool->clear(); + ResetInternal(); ResetScaleTuning(); ResetSuspendedRegions(); @@ -79,6 +97,11 @@ dmsg(1,("OK\n")); } + if (pNotePool) { + pNotePool->clear(); + delete pNotePool; + } + if (pVoicePool) { pVoicePool->clear(); delete pVoicePool; @@ -235,18 +258,30 @@ pChannel->ResetRegionsInUse(pRegionPool); } + // FIXME: Shouldn't all those pool elements be freed before resizing the pools? try { pVoicePool->resizePool(iVoices); + pNotePool->resizePool(iVoices * MAX_NOTES_HEADROOM); + noteIDPool.resizePool(iVoices * MAX_NOTES_HEADROOM); } catch (...) { throw Exception("FATAL: Could not resize voice pool!"); } - for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { + for (VoiceIterator iterVoice = pVoicePool->allocAppend(); + iterVoice; iterVoice = pVoicePool->allocAppend()) + { iterVoice->SetEngine(this); iterVoice->pDiskThread = this->pDiskThread; } pVoicePool->clear(); + for (NoteIterator itNote = pNotePool->allocAppend(); itNote; + itNote = pNotePool->allocAppend()) + { + itNote->init(pVoicePool, ¬eIDPool); + } + pNotePool->clear(); + PostSetMaxVoices(iVoices); ResumeAll(); } @@ -315,6 +350,7 @@ MinFadeOutSamples = MaxSamplesPerCycle; // lower minimum release time const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; + pVoicePool->clear(); for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { iterVoice->CalculateFadeOutCoeff(minReleaseTime, SampleRate); } @@ -335,6 +371,7 @@ exit(EXIT_FAILURE); } + pVoicePool->clear(); for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { iterVoice->pDiskThread = this->pDiskThread; dmsg(3,("d")); @@ -432,14 +469,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 +486,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() { @@ -564,8 +601,10 @@ return pRegionPool[index]; } - // implementation of abstract method derived from class 'LinuxSampler::VoicePool' - virtual Pool* GetVoicePool() { return pVoicePool; } + // implementation of abstract methods derived from class 'LinuxSampler::NotePool' + virtual Pool* GetVoicePool() OVERRIDE { return pVoicePool; } + virtual Pool< Note >* GetNotePool() OVERRIDE { return pNotePool; } + virtual Pool* GetNodeIDPool() OVERRIDE { return ¬eIDPool; } D* GetDiskThread() { return pDiskThread; } @@ -585,7 +624,8 @@ } virtual bool Process(MidiKey* pMidiKey) OVERRIDE { - VoiceIterator itVoice = pMidiKey->pActiveVoices->first(); + NoteIterator itNote = pMidiKey->pActiveNotes->first(); + VoiceIterator itVoice = itNote->pActiveVoices->first(); // if current key is not associated with this region, skip this key if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false; @@ -608,14 +648,98 @@ int ActiveVoiceCountTemp; ///< number of currently active voices (for internal usage, will be used for incrementation) VoiceIterator itLastStolenVoice; ///< Only for voice stealing: points to the last voice which was theft in current audio fragment, NULL otherwise. + NoteIterator itLastStolenNote; ///< Only for voice stealing: points to the last note from which was theft in current audio fragment, NULL otherwise. RTList::Iterator iuiLastStolenKey; ///< Only for voice stealing: key number of last key on which the last voice was theft in current audio fragment, NULL otherwise. EngineChannelBase* pLastStolenChannel; ///< Only for voice stealing: points to the engine channel on which the previous voice was stolen in this audio fragment. VoiceIterator itLastStolenVoiceGlobally; ///< Same as itLastStolenVoice, but engine globally + NoteIterator itLastStolenNoteGlobally; ///< Same as itLastStolenNote, but engine globally RTList::Iterator iuiLastStolenKeyGlobally; ///< Same as iuiLastStolenKey, but engine globally RTList* pVoiceStealingQueue; ///< All voice-launching events which had to be postponed due to free voice shortage. Mutex ResetInternalMutex; ///< Mutex to protect the ResetInternal function for concurrent usage (e.g. by the lscp and instrument loader threads). int iMaxDiskStreams; + NoteBase* NoteByID(note_id_t id) OVERRIDE { + NoteIterator itNote = GetNotePool()->fromID(id); + if (!itNote) return NULL; + return &*itNote; + } + + /** + * Gets a new @c Note object from the note pool, initializes it + * appropriately, links it with requested parent note (if + * requested), moves it to the appropriate key's list of active + * notes it, and sticks the new note's unique ID to the + * passed @a pNoteOnEvent. + * + * @param pEngineChannel - engine channel on which this event happened + * @param pNoteOnEvent - event which caused this + * @returns new note's unique ID (or zero on error) + */ + note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Event* pNoteOnEvent) OVERRIDE { + EngineChannelBase* pChannel = static_cast*>(pEngineChannel); + Pool< Note >* pNotePool = GetNotePool(); + + if (pNotePool->poolIsEmpty()) { + dmsg(1,("Engine: Could not launch new note; Note pool empty!\n")); + return 0; // error + } + + // create a new note (for new voices to be assigned to) + //NoteIterator itNewNote = pKey->pActiveNotes->allocAppend(); + NoteIterator itNewNote = pNotePool->allocAppend(); + const note_id_t newNoteID = pNotePool->getID(itNewNote); + + // 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 + // note, but rather a child note, then this new note will be + // allocated on the parent note's key instead in order to + // release the child note simultaniously with its parent note + itNewNote->hostKey = pNoteOnEvent->Param.Note.Key; + + // in case this new note was requested to be a child note, + // then retrieve its parent note and link them with each other + const note_id_t parentNoteID = pNoteOnEvent->Param.Note.ParentNoteID; + if (parentNoteID) { + NoteIterator itParentNote = pNotePool->fromID(parentNoteID); + if (itParentNote) { + RTList::Iterator itChildNoteID = itParentNote->pChildNotes->allocAppend(); + if (itChildNoteID) { + // link parent and child note with each other + *itChildNoteID = newNoteID; + itNewNote->parentNoteID = parentNoteID; + itNewNote->hostKey = itParentNote->hostKey; + } else { + dmsg(1,("Engine: Could not assign new note as child note; Note ID pool empty!\n")); + pNotePool->free(itNewNote); + return 0; // error + } + } else { + // the parent note was apparently released already, so + // free the new note again and inform caller that it + // should drop the event + dmsg(3,("Engine: Could not assign new note as child note; Parent note is gone!\n")); + pNotePool->free(itNewNote); + return 0; // error + } + } + + dmsg(2,("Launched new note on host key %d\n", itNewNote->hostKey)); + + // copy event which caused this note + itNewNote->cause = *pNoteOnEvent; + itNewNote->eventID = pEventPool->getID(pNoteOnEvent); + + // move new note to its host key + MidiKey* pKey = &pChannel->pMIDIKeyInfo[itNewNote->hostKey]; + itNewNote.moveToEndOf(pKey->pActiveNotes); + + // assign unique note ID of this new note to the original note on event + pNoteOnEvent->Param.Note.ID = newNoteID; + + return newNoteID; // success + } + /** * Dispatch and handle all events in this audio fragment for the given * engine channel. @@ -634,17 +758,18 @@ // 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) - } + 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) { @@ -668,6 +793,47 @@ 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 @@ -707,26 +873,91 @@ // reset voice stealing for the next engine channel (or next audio fragment) itLastStolenVoice = VoiceIterator(); itLastStolenVoiceGlobally = VoiceIterator(); + itLastStolenNote = NoteIterator(); + itLastStolenNoteGlobally = NoteIterator(); iuiLastStolenKey = RTList::Iterator(); iuiLastStolenKeyGlobally = RTList::Iterator(); 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->pScript->pEvents->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; @@ -735,28 +966,56 @@ // initialize/reset other members itScriptEvent->cause = *itEvent; - itScriptEvent->id = pEventPool->getID(itEvent); itScriptEvent->currentHandler = 0; itScriptEvent->executionSlices = 0; + // this is the native representation of the $EVENT_ID script variable + itScriptEvent->id = + (itEvent->Type == Event::type_note_on) + ? ScriptID::fromNoteID( itEvent->Param.Note.ID ) + : ScriptID::fromEventID( pEventPool->getID(itEvent) ); // 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); + // 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. * * Will be called to resume execution of a real-time instrument - * script event which has been suspended in a previous audio - * fragment cycle. + * script event which has been suspended previously. * * Script execution might be suspended for various reasons. Usually * a script will be suspended if the script called the built-in @@ -770,16 +1029,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->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); + + // 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); + } + } } /** @@ -788,11 +1075,11 @@ * 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 */ - int StealVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { + int StealVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { if (VoiceSpawnsLeft <= 0) { dmsg(1,("Max. voice thefts per audio fragment reached (you may raise CONFIG_MAX_VOICES).\n")); return -1; @@ -800,99 +1087,124 @@ EngineChannelBase* pEngineChn = static_cast*>(pEngineChannel); - if (!pEventPool->poolIsEmpty()) { + if (pEventPool->poolIsEmpty()) { + dmsg(1,("Event pool emtpy!\n")); + return -1; + } - if(!pEngineChn->StealVoice(itNoteOnEvent, &itLastStolenVoice, &iuiLastStolenKey)) { - --VoiceSpawnsLeft; - return 0; - } + if (!pEngineChn->StealVoice(itNoteOnEvent, &itLastStolenVoice, &itLastStolenNote, &iuiLastStolenKey)) { + --VoiceSpawnsLeft; + return 0; + } - // if we couldn't steal a voice from the same engine channel then - // steal oldest voice on the oldest key from any other engine channel - // (the smaller engine channel number, the higher priority) - EngineChannelBase* pSelectedChannel; - int iChannelIndex; - VoiceIterator itSelectedVoice; - - // select engine channel - if (pLastStolenChannel) { - pSelectedChannel = pLastStolenChannel; - iChannelIndex = pSelectedChannel->iEngineIndexSelf; - } else { // pick the engine channel followed by this engine channel - iChannelIndex = (pEngineChn->iEngineIndexSelf + 1) % engineChannels.size(); - pSelectedChannel = static_cast*>(engineChannels[iChannelIndex]); - } - - // if we already stole in this fragment, try to proceed on same key - if (this->itLastStolenVoiceGlobally) { - itSelectedVoice = this->itLastStolenVoiceGlobally; - do { - ++itSelectedVoice; - } while (itSelectedVoice && !itSelectedVoice->IsStealable()); // proceed iterating if voice was created in this fragment cycle + // if we couldn't steal a voice from the same engine channel then + // steal oldest voice on the oldest key from any other engine channel + // (the smaller engine channel number, the higher priority) + EngineChannelBase* pSelectedChannel; + int iChannelIndex; + VoiceIterator itSelectedVoice; + + // select engine channel + if (pLastStolenChannel) { + pSelectedChannel = pLastStolenChannel; + iChannelIndex = pSelectedChannel->iEngineIndexSelf; + } else { // pick the engine channel followed by this engine channel + iChannelIndex = (pEngineChn->iEngineIndexSelf + 1) % engineChannels.size(); + pSelectedChannel = static_cast*>(engineChannels[iChannelIndex]); + } + + // if we already stole in this fragment, try to proceed on same note + if (this->itLastStolenVoiceGlobally) { + itSelectedVoice = this->itLastStolenVoiceGlobally; + do { + ++itSelectedVoice; + } while (itSelectedVoice && !itSelectedVoice->IsStealable()); // proceed iterating if voice was created in this fragment cycle + } + // did we find a 'stealable' voice? + if (itSelectedVoice && itSelectedVoice->IsStealable()) { + // remember which voice we stole, so we can simply proceed on next voice stealing + this->itLastStolenVoiceGlobally = itSelectedVoice; + // done + goto stealable_voice_found; + } + + // get (next) oldest note + if (this->itLastStolenNoteGlobally) { + for (NoteIterator itNote = ++this->itLastStolenNoteGlobally; + itNote; ++itNote) + { + for (itSelectedVoice = itNote->pActiveVoices->first(); itSelectedVoice; ++itSelectedVoice) { + // proceed iterating if voice was created in this audio fragment cycle + if (itSelectedVoice->IsStealable()) { + // remember which voice of which note we stole, so we can simply proceed on next voice stealing + this->itLastStolenNoteGlobally = itNote; + this->itLastStolenVoiceGlobally = itSelectedVoice; + goto stealable_voice_found; // selection succeeded + } + } } + } - #if CONFIG_DEVMODE - EngineChannel* pBegin = pSelectedChannel; // to detect endless loop - #endif // CONFIG_DEVMODE - - // did we find a 'stealable' voice? - if (itSelectedVoice && itSelectedVoice->IsStealable()) { - // remember which voice we stole, so we can simply proceed on next voice stealing - this->itLastStolenVoiceGlobally = itSelectedVoice; - } else while (true) { // iterate through engine channels - // get (next) oldest key - RTList::Iterator iuiSelectedKey = (this->iuiLastStolenKeyGlobally) ? ++this->iuiLastStolenKeyGlobally : pSelectedChannel->pActiveKeys->first(); - this->iuiLastStolenKeyGlobally = RTList::Iterator(); // to prevent endless loop (see line above) - while (iuiSelectedKey) { - MidiKey* pSelectedKey = &pSelectedChannel->pMIDIKeyInfo[*iuiSelectedKey]; - itSelectedVoice = pSelectedKey->pActiveVoices->first(); + #if CONFIG_DEVMODE + EngineChannel* pBegin = pSelectedChannel; // to detect endless loop + #endif // CONFIG_DEVMODE + + while (true) { // iterate through engine channels + // get (next) oldest key + RTList::Iterator iuiSelectedKey = (this->iuiLastStolenKeyGlobally) ? ++this->iuiLastStolenKeyGlobally : pSelectedChannel->pActiveKeys->first(); + this->iuiLastStolenKeyGlobally = RTList::Iterator(); // to prevent endless loop (see line above) + while (iuiSelectedKey) { + MidiKey* pSelectedKey = &pSelectedChannel->pMIDIKeyInfo[*iuiSelectedKey]; + + for (NoteIterator itNote = pSelectedKey->pActiveNotes->first(), + itNotesEnd = pSelectedKey->pActiveNotes->end(); + itNote != itNotesEnd; ++itNote) + { + itSelectedVoice = itNote->pActiveVoices->first(); // proceed iterating if voice was created in this fragment cycle while (itSelectedVoice && !itSelectedVoice->IsStealable()) ++itSelectedVoice; // found a "stealable" voice ? if (itSelectedVoice && itSelectedVoice->IsStealable()) { - // remember which voice on which key on which engine channel we stole, so we can simply proceed on next voice stealing + // remember which voice of which note on which key on which engine channel we stole, so we can simply proceed on next voice stealing this->iuiLastStolenKeyGlobally = iuiSelectedKey; + this->itLastStolenNoteGlobally = itNote; this->itLastStolenVoiceGlobally = itSelectedVoice; this->pLastStolenChannel = pSelectedChannel; goto stealable_voice_found; // selection succeeded } - ++iuiSelectedKey; // get next key on current engine channel } - // get next engine channel - iChannelIndex = (iChannelIndex + 1) % engineChannels.size(); - pSelectedChannel = static_cast*>(engineChannels[iChannelIndex]); - - #if CONFIG_DEVMODE - if (pSelectedChannel == pBegin) { - dmsg(1,("FATAL ERROR: voice stealing endless loop!\n")); - dmsg(1,("VoiceSpawnsLeft=%d.\n", VoiceSpawnsLeft)); - dmsg(1,("Exiting.\n")); - exit(-1); - } - #endif // CONFIG_DEVMODE + ++iuiSelectedKey; // get next key on current engine channel } - - // jump point if a 'stealable' voice was found - stealable_voice_found: + // get next engine channel + iChannelIndex = (iChannelIndex + 1) % engineChannels.size(); + pSelectedChannel = static_cast*>(engineChannels[iChannelIndex]); #if CONFIG_DEVMODE - if (!itSelectedVoice->IsActive()) { - dmsg(1,("EngineBase: ERROR, tried to steal a voice which was not active !!!\n")); - return -1; + if (pSelectedChannel == pBegin) { + dmsg(1,("FATAL ERROR: voice stealing endless loop!\n")); + dmsg(1,("VoiceSpawnsLeft=%d.\n", VoiceSpawnsLeft)); + dmsg(1,("Exiting.\n")); + exit(-1); } #endif // CONFIG_DEVMODE + } - // now kill the selected voice - itSelectedVoice->Kill(itNoteOnEvent); - - --VoiceSpawnsLeft; + // jump point if a 'stealable' voice was found + stealable_voice_found: - return 0; // success - } - else { - dmsg(1,("Event pool emtpy!\n")); + #if CONFIG_DEVMODE + if (!itSelectedVoice->IsActive()) { + dmsg(1,("EngineBase: ERROR, tried to steal a voice which was not active !!!\n")); return -1; } + #endif // CONFIG_DEVMODE + + // now kill the selected voice + itSelectedVoice->Kill(itNoteOnEvent); + + --VoiceSpawnsLeft; + + return 0; // success } void HandleInstrumentChanges() { @@ -914,7 +1226,8 @@ dmsg(5,("Engine: instrument change command received\n")); cmd.bChangeInstrument = false; pEngineChannel->pInstrument = cmd.pInstrument; - pEngineChannel->pScript = cmd.pScript; + pEngineChannel->pScript = + cmd.pScript->bHasValidScript ? cmd.pScript : NULL; instrumentChanged = true; pEngineChannel->MarkAllActiveVoicesAsOrphans(); @@ -985,9 +1298,23 @@ EngineChannelBase* pEngineChannel = static_cast*>(itVoiceStealEvent->pEngineChannel);; if (!pEngineChannel->pInstrument) continue; // ignore if no instrument loaded + PoolVoiceIterator itNewVoice = LaunchVoice(pEngineChannel, itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false, false); if (itNewVoice) { + // usually there should already be a new Note object + NoteIterator itNote = GetNotePool()->fromID(itVoiceStealEvent->Param.Note.ID); + if (!itNote) { // should not happen, but just to be sure ... + const note_id_t noteID = LaunchNewNote(pEngineChannel, &*itVoiceStealEvent); + if (!noteID) { + dmsg(1,("Engine: Voice stealing failed; No Note object and Note pool empty!\n")); + continue; + } + itNote = GetNotePool()->fromID(noteID); + } + // move voice from whereever it was, to the new note's list of active voices + itNewVoice = itNewVoice.moveToEndOf(itNote->pActiveVoices); + // render audio of this new voice for the first time itNewVoice->Render(Samples); if (itNewVoice->IsActive()) { // still active *(pEngineChannel->pRegionsInUse->allocAppend()) = itNewVoice->GetRegion(); @@ -1025,7 +1352,8 @@ pChannel->FreeAllInactiveKyes(); // empty the engine channel's own event lists - pChannel->ClearEventLists(); + // (only events of the current audio fragment cycle) + pChannel->ClearEventListsOfCurrentFragment(); } /** @@ -1090,7 +1418,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; @@ -1098,7 +1426,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; @@ -1251,21 +1579,17 @@ /** * 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) { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); - //HACK: we should better add the transpose value only to the most mandatory places (like for retrieving the region and calculating the tuning), because otherwise voices will unintendedly survive when changing transpose while playing - int k = itNoteOnEvent->Param.Note.Key + pChannel->GlobalTranspose; - if (k < 0 || k > 127) return; //ignore keys outside the key range - - itNoteOnEvent->Param.Note.Key += pChannel->GlobalTranspose; - int vel = itNoteOnEvent->Param.Note.Velocity; - const int key = itNoteOnEvent->Param.Note.Key; + const int vel = itNoteOnEvent->Param.Note.Velocity; + if (key < 0 || key > 127) return; // ignore event, key outside allowed key range + MidiKey* pKey = &pChannel->pMIDIKeyInfo[key]; pChannel->listeners.PreProcessNoteOn(key, vel); @@ -1293,15 +1617,20 @@ if (pOtherKey->Active) { // get final portamento position of currently active voice if (pChannel->PortamentoMode) { - VoiceIterator itVoice = pOtherKey->pActiveVoices->last(); - if (itVoice) itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList); + NoteIterator itNote = pOtherKey->pActiveNotes->last(); + if (itNote) { + VoiceIterator itVoice = itNote->pActiveVoices->last(); + if (itVoice) itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList); + } } // kill all voices on the (other) key - VoiceIterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); - VoiceIterator end = pOtherKey->pActiveVoices->end(); - for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) - itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList); + for (NoteIterator itNote = pOtherKey->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(itNoteOnEventOnKeyList); + } } } } @@ -1363,20 +1692,16 @@ * 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) { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); - int k = itNoteOffEvent->Param.Note.Key + pChannel->GlobalTranspose; - if (k < 0 || k > 127) return; //ignore keys outside the key range - - //HACK: we should better add the transpose value only to the most mandatory places (like for retrieving the region and calculating the tuning), because otherwise voices will unintendedly survive when changing transpose while playing - itNoteOffEvent->Param.Note.Key += pChannel->GlobalTranspose; - int vel = itNoteOffEvent->Param.Note.Velocity; - const int iKey = itNoteOffEvent->Param.Note.Key; + const int vel = itNoteOffEvent->Param.Note.Velocity; + if (iKey < 0 || iKey > 127) return; // ignore event, key outside allowed key range + MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey]; pChannel->listeners.PreProcessNoteOff(iKey, vel); @@ -1410,7 +1735,8 @@ pChannel->SoloKey = i; // get final portamento position of currently active voice if (pChannel->PortamentoMode) { - VoiceIterator itVoice = pKey->pActiveVoices->first(); + NoteIterator itNote = pKey->pActiveNotes->first(); + VoiceIterator itVoice = itNote->pActiveVoices->first(); if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList); } // create a pseudo note on event @@ -1422,8 +1748,11 @@ itPseudoNoteOnEvent->Type = Event::type_note_on; itPseudoNoteOnEvent->Param.Note.Key = i; itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity; - // allocate and trigger new voice(s) for the other key - TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false); + // 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); @@ -1436,11 +1765,13 @@ if (bOtherKeysPressed) { if (pKey->Active) { // kill all voices on this key bShouldRelease = false; // no need to release, as we kill it here - VoiceIterator itVoiceToBeKilled = pKey->pActiveVoices->first(); - VoiceIterator end = pKey->pActiveVoices->end(); - for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) - itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); + 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; @@ -1452,7 +1783,11 @@ // spawn release triggered voice(s) if needed if (pKey->ReleaseTrigger && pChannel->pInstrument) { - TriggerReleaseVoices(pChannel, itNoteOffEventOnKeyList); + // 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; } } @@ -1481,16 +1816,35 @@ pVoiceStealingQueue->clear(); itLastStolenVoice = VoiceIterator(); itLastStolenVoiceGlobally = VoiceIterator(); + itLastStolenNote = NoteIterator(); + itLastStolenNoteGlobally = NoteIterator(); iuiLastStolenKey = RTList::Iterator(); iuiLastStolenKeyGlobally = RTList::Iterator(); pLastStolenChannel = NULL; + // reset all notes + pNotePool->clear(); + for (NoteIterator itNote = pNotePool->allocAppend(); itNote; + itNote = pNotePool->allocAppend()) + { + itNote->reset(); + } + pNotePool->clear(); + // reset all voices + pVoicePool->clear(); for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { iterVoice->Reset(); } 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(); @@ -1519,7 +1873,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) @@ -1562,7 +1916,7 @@ // launch the new voice if (itNewVoice->Trigger(pChannel, itNoteOnEvent, pChannel->Pitch, pRegion, VoiceType, iKeyGroup) < 0) { dmsg(4,("Voice not triggered\n")); - pKey->pActiveVoices->free(itNewVoice); + GetVoicePool()->free(itNewVoice); } else { // on success --VoiceSpawnsLeft; @@ -1611,6 +1965,8 @@ } private: + Pool< Note >* pNotePool; + Pool noteIDPool; Pool* pVoicePool; ///< Contains all voices that can be activated. Pool SuspendedRegions; Mutex SuspendedRegionsMutex;