--- linuxsampler/trunk/src/engines/EngineChannelBase.h 2012/03/17 06:19:01 2335 +++ linuxsampler/trunk/src/engines/EngineChannelBase.h 2017/01/05 16:04:00 3073 @@ -5,6 +5,7 @@ * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2012-2017 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 * @@ -39,6 +40,7 @@ bool bChangeInstrument; ///< Set to true by the loader when the channel should change instrument. I* pInstrument; ///< The new instrument. Also used by the loader to read the previously loaded instrument. RTList* pRegionsInUse; ///< List of dimension regions in use by the currently loaded instrument. Continuously updated by the audio thread. + InstrumentScript* pScript; ///< Instrument script to be executed for this instrument. This is never NULL, it is always a valid InstrumentScript pointer. Use InstrumentScript::bHasValidScript whether it reflects a valid instrument script to be executed. }; template @@ -48,17 +50,39 @@ }; template - class VoicePool { + class NotePool { public: + /** + * Pool from where Voice objects are allocated from (and freed back to). + */ virtual Pool* GetVoicePool() = 0; + + /** + * Pool from where new Note objects are allocated from (and freed back to). + */ + virtual Pool< Note >* GetNotePool() = 0; + + /** + * Pool for saving already existing Note object IDs somewhere. + * + * @b IMPORTANT: This pool is @b NOT used for generating any IDs for + * Note objects! The respective Note objective IDs are emitted by + * the Note object pool (see GetNotePool() above). + */ + virtual Pool* GetNoteIDPool() = 0; }; template class EngineChannelBase: public AbstractEngineChannel, public MidiKeyboardManager, public ResourceConsumer { public: + typedef typename RTList< Note >::Iterator NoteIterator; typedef typename RTList::Iterator RTListRegionIterator; typedef typename MidiKeyboardManager::MidiKey MidiKey; + virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE { + return this; + } + virtual void HandBack(I* Instrument) { ResourceManager* mgr = dynamic_cast*>(pEngine->GetInstrumentManager()); @@ -84,9 +108,11 @@ } virtual void DeleteRegionsInUse() { + RTList* previous = NULL; // prevent double free { InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); if (cmd.pRegionsInUse) { + previous = cmd.pRegionsInUse; delete cmd.pRegionsInUse; cmd.pRegionsInUse = NULL; } @@ -95,7 +121,8 @@ { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); if (cmd.pRegionsInUse) { - delete cmd.pRegionsInUse; + if (cmd.pRegionsInUse != previous) + delete cmd.pRegionsInUse; cmd.pRegionsInUse = NULL; } cmd.bChangeInstrument = false; @@ -115,17 +142,19 @@ } } - virtual void Connect(AudioOutputDevice* pAudioOut) { + virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE { if (pEngine) { if (pEngine->pAudioOutputDevice == pAudioOut) return; DisconnectAudioOutputDevice(); } AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut); - EngineMutex.Lock(); - pEngine = newEngine; - EngineMutex.Unlock(); - ResetInternal(); + { + LockGuard lock(EngineMutex); + pEngine = newEngine; + } + ResetInternal(false/*don't reset engine*/); // 'false' is error prone here, but the danger of recursion with 'true' would be worse, there could be a better solution though pEvents = new RTList(pEngine->pEventPool); + delayedEvents.pList = new RTList(pEngine->pEventPool); RegionPools* pRegionPool = dynamic_cast*>(pEngine); // reset the instrument change command struct (need to be done @@ -152,9 +181,12 @@ bStatusChanged = true; } - VoicePool* pVoicePool = dynamic_cast*>(pEngine); - MidiKeyboardManager::AllocateActiveVoices(pVoicePool->GetVoicePool()); - MidiKeyboardManager::AllocateEvents(pEngine->pEventPool); + NotePool* pNotePool = dynamic_cast*>(pEngine); + MidiKeyboardManager::AllocateActiveNotesLists( + pNotePool->GetNotePool(), + pNotePool->GetVoicePool() + ); + MidiKeyboardManager::AllocateEventsLists(pEngine->pEventPool); AudioDeviceChannelLeft = 0; AudioDeviceChannelRight = 1; @@ -172,12 +204,13 @@ MidiInputPort::AddSysexListener(pEngine); } - virtual void DisconnectAudioOutputDevice() { + virtual void DisconnectAudioOutputDevice() OVERRIDE { if (pEngine) { // if clause to prevent disconnect loops - ResetInternal(); + ResetInternal(false/*don't reset engine*/); // 'false' is error prone here, but the danger of recursion with 'true' would be worse, there could be a better solution though DeleteRegionsInUse(); + UnloadScriptInUse(); InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); if (cmd.pInstrument) { @@ -189,15 +222,20 @@ delete pEvents; pEvents = NULL; } + if (delayedEvents.pList) { + delete delayedEvents.pList; + delayedEvents.pList = NULL; + } - MidiKeyboardManager::DeleteActiveVoices(); - MidiKeyboardManager::DeleteEvents(); + MidiKeyboardManager::DeleteActiveNotesLists(); + MidiKeyboardManager::DeleteEventsLists(); DeleteGroupEventLists(); AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice; - EngineMutex.Lock(); - pEngine = NULL; - EngineMutex.Unlock(); + { + LockGuard lock(EngineMutex); + pEngine = NULL; + } AbstractEngine::FreeEngine(this, oldAudioDevice); AudioDeviceChannelLeft = -1; AudioDeviceChannelRight = -1; @@ -215,14 +253,22 @@ virtual bool Process(MidiKey* pMidiKey) { pMidiKey->pEvents->clear(); return false; } }; - void ClearEventLists() { + /** + * Free all events of the current audio fragment cycle. Calling + * this method will @b NOT free events scheduled past the current + * fragment's boundary! (@see AbstractEngineChannel::delayedEvents). + */ + void ClearEventListsOfCurrentFragment() { pEvents->clear(); // empty MIDI key specific event lists ClearEventListsHandler handler; this->ProcessActiveVoices(&handler); // empty exclusive group specific event lists - ClearGroupEventLists(); + // (pInstrument == 0 could mean that LoadInstrument is + // building new group event lists, so we must check + // for that) + if (pInstrument) ClearGroupEventLists(); } // implementation of abstract methods derived from interface class 'InstrumentConsumer' @@ -232,10 +278,10 @@ * we are currently using on this EngineChannel is going to be updated, * so we can stop playback before that happens. */ - virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) { + virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) OVERRIDE { dmsg(3,("EngineChannelBase: Received instrument update message.\n")); if (pEngine) pEngine->DisableAndLock(); - ResetInternal(); + ResetInternal(false/*don't reset engine*/); this->pInstrument = NULL; } @@ -243,7 +289,7 @@ * Will be called by the InstrumentResourceManager when the instrument * update process was completed, so we can continue with playback. */ - virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) { + virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) OVERRIDE { this->pInstrument = pNewResource; //TODO: there are couple of engine parameters we should update here as well if the instrument was updated (see LoadInstrument()) if (pEngine) pEngine->Enable(); bStatusChanged = true; // status of engine has changed, so set notify flag @@ -255,12 +301,24 @@ * * @param fProgress - current progress as value between 0.0 and 1.0 */ - virtual void OnResourceProgress(float fProgress) { + virtual void OnResourceProgress(float fProgress) OVERRIDE { this->InstrumentStat = int(fProgress * 100.0f); - dmsg(7,("EngineChannelBase: progress %d%", InstrumentStat)); + dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat)); bStatusChanged = true; // status of engine has changed, so set notify flag } + /** + * 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(RTList::Iterator& itEvent) OVERRIDE { + if (!pEngine) return; + pEngine->ProcessReleaseTrigger(this, itEvent); + } + void RenderActiveVoices(uint Samples) { RenderVoicesHandler handler(this, Samples); this->ProcessActiveVoices(&handler); @@ -269,13 +327,112 @@ SetDiskStreamCount(handler.StreamCount); } + /** + * Called by real-time instrument script functions to schedule a + * new note (new note-on event and a new @c Note object linked to it) + * @a delay microseconds in future. + * + * @b IMPORTANT: for the supplied @a delay to be scheduled + * correctly, the passed @a pEvent must be assigned a valid + * fragment time within the current audio fragment boundaries. That + * fragment time will be used by this method as basis for + * interpreting what "now" acutally is, and thus it will be used as + * basis for calculating the precise scheduling time for @a delay. + * The easiest way to achieve this is by copying a recent event + * which happened within the current audio fragment cycle: i.e. the + * original event which caused calling this method here, or by using + * Event::copyTimefrom() method to only copy the time, without any + * other event data. + * + * @param pEvent - note-on event to be scheduled in future (event + * data will be copied) + * @param delay - amount of microseconds in future (from now) when + * event shall be processed + * @returns unique note ID of scheduled new note, or NULL on error + */ + note_id_t ScheduleNoteMicroSec(const Event* pEvent, int delay) OVERRIDE { + // add (copied) note-on event into scheduler queue + const event_id_t noteOnEventID = ScheduleEventMicroSec(pEvent, delay); + if (!noteOnEventID) return 0; // error + // get access to (copied) event on the scheduler queue + RTList::Iterator itEvent = pEvents->fromID(noteOnEventID); + // stick a new note to the (copied) event on the queue + const note_id_t noteID = pEngine->LaunchNewNote(this, &*itEvent); + return noteID; + } + + /** + * Called by real-time instrument script functions to ignore the note + * reflected by given note ID. The note's event will be freed immediately + * to its event pool and this will prevent voices to be launched for the + * note. + * + * NOTE: preventing a note by calling this method works only if the note + * was launched within the current audio fragment cycle. + * + * @param id - unique ID of note to be dropped + */ + void IgnoreNote(note_id_t id) OVERRIDE { + Pool< Note >* pNotePool = + dynamic_cast*>(pEngine)->GetNotePool(); + + NoteIterator itNote = pNotePool->fromID(id); + if (!itNote) return; // note probably already released + + // if the note already got active voices, then it is too late to drop it + if (!itNote->pActiveVoices->isEmpty()) return; + + // if the original (note-on) event is not available anymore, then it is too late to drop it + RTList::Iterator itEvent = pEvents->fromID(itNote->eventID); + if (!itEvent) return; + + // drop the note + pNotePool->free(itNote); + + // drop the original event + pEvents->free(itEvent); + } + + /** + * Copies the note IDs of all currently active notes on this engine + * channel to the note ID buffer @a dstBuf, and returns the amount + * of note IDs that have been copied to the destination buffer. + * + * @param dstBuf - destination buffer for note IDs + * @param bufSize - size of destination buffer (as amount of max. + * note IDs, not as amount of bytes) + * @returns amount of note IDs that have been copied to buffer + */ + uint AllNoteIDs(note_id_t* dstBuf, uint bufSize) OVERRIDE { + uint n = 0; + + Pool< Note >* pNotePool = + dynamic_cast*>(pEngine)->GetNotePool(); + + RTList::Iterator iuiKey = this->pActiveKeys->first(); + RTList::Iterator end = this->pActiveKeys->end(); + for(; iuiKey != end; ++iuiKey) { + MidiKey* pKey = &this->pMIDIKeyInfo[*iuiKey]; + NoteIterator itNote = pKey->pActiveNotes->first(); + for (; itNote; ++itNote) { + if (n >= bufSize) goto done; + dstBuf[n++] = pNotePool->getID(itNote); + } + } + done: + return n; + } + RTList* pRegionsInUse; ///< temporary pointer into the instrument change command, used by the audio thread I* pInstrument; template friend class EngineBase; protected: - EngineChannelBase() : InstrumentChangeCommandReader(InstrumentChangeCommand) { + EngineChannelBase() : + MidiKeyboardManager(this), + InstrumentChangeCommandReader(InstrumentChangeCommand) + { pInstrument = NULL; // reset the instrument change command struct (need to be done @@ -284,17 +441,37 @@ InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); cmd.pRegionsInUse = NULL; cmd.pInstrument = NULL; + cmd.pScript = new InstrumentScript(this); cmd.bChangeInstrument = false; } { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); cmd.pRegionsInUse = NULL; cmd.pInstrument = NULL; + cmd.pScript = new InstrumentScript(this); cmd.bChangeInstrument = false; } } - virtual ~EngineChannelBase() { } + virtual ~EngineChannelBase() { + InstrumentScript* previous = NULL; // prevent double free + { + InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); + if (cmd.pScript) { + previous = cmd.pScript; + delete cmd.pScript; + cmd.pScript = NULL; + } + } + { + InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); + if (cmd.pScript) { + if (previous != cmd.pScript) + delete cmd.pScript; + cmd.pScript = NULL; + } + } + } typedef typename RTList::Iterator RTListVoiceIterator; @@ -306,7 +483,7 @@ EngineChannelBase* pChannel; RenderVoicesHandler(EngineChannelBase* channel, uint samples) : - pChannel(channel), Samples(samples), VoiceCount(0), StreamCount(0) { } + Samples(samples), VoiceCount(0), StreamCount(0), pChannel(channel) { } virtual void Process(RTListVoiceIterator& itVoice) { // now render current voice @@ -333,13 +510,13 @@ SyncConfInstrChangeCmdReader InstrumentChangeCommandReader; /** This method is not thread safe! */ - virtual void ResetInternal() { - AbstractEngineChannel::ResetInternal(); + virtual void ResetInternal(bool bResetEngine) OVERRIDE { + AbstractEngineChannel::ResetInternal(bResetEngine); MidiKeyboardManager::Reset(); } - virtual void ResetControllers() { + virtual void ResetControllers() OVERRIDE { AbstractEngineChannel::ResetControllers(); MidiKeyboardManager::SustainPedal = false; @@ -347,6 +524,35 @@ } /** + * Unload the currently used and loaded real-time instrument script. + * The source code of the script is retained, so that it can still + * be reloaded. + */ + void UnloadScriptInUse() { + { + InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); + if (cmd.pScript) cmd.pScript->unload(); + } + { + InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); + if (cmd.pScript) cmd.pScript->unload(); + } + InstrumentChangeCommand.SwitchConfig(); // switch back to original one + } + + /** + * Load real-time instrument script and all its resources required + * for the upcoming instrument change. + * + * @param text - source code of script + */ + void LoadInstrumentScript(const String& text) { + InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); + // load the new script + cmd.pScript->load(text); + } + + /** * Changes the instrument for an engine channel. * * @param pInstrument - new instrument