--- linuxsampler/trunk/src/engines/EngineChannelBase.h 2014/06/10 13:32:16 2612 +++ linuxsampler/trunk/src/engines/EngineChannelBase.h 2016/10/31 00:05:00 3034 @@ -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 * @@ -39,7 +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, or NULL if instrument does not have a script. + 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 @@ -49,17 +50,24 @@ }; template - class VoicePool { + class NotePool { public: virtual Pool* GetVoicePool() = 0; + virtual Pool< Note >* GetNotePool() = 0; + virtual Pool* GetNodeIDPool() = 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()); @@ -129,8 +137,9 @@ LockGuard lock(EngineMutex); pEngine = newEngine; } - 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 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 @@ -157,9 +166,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; @@ -180,7 +192,7 @@ virtual void DisconnectAudioOutputDevice() { 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(); @@ -195,9 +207,13 @@ 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; @@ -222,7 +238,12 @@ 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; @@ -245,7 +266,7 @@ 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; } @@ -267,10 +288,22 @@ */ 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); @@ -279,13 +312,82 @@ 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); + } + 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 @@ -336,7 +438,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 @@ -363,8 +465,8 @@ SyncConfInstrChangeCmdReader InstrumentChangeCommandReader; /** This method is not thread safe! */ - virtual void ResetInternal() { - AbstractEngineChannel::ResetInternal(); + virtual void ResetInternal(bool bResetEngine) OVERRIDE { + AbstractEngineChannel::ResetInternal(bResetEngine); MidiKeyboardManager::Reset(); } @@ -384,12 +486,13 @@ void UnloadScriptInUse() { { InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); - if (cmd.pScript) pScript->unload(); + if (cmd.pScript) cmd.pScript->unload(); } { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); - if (cmd.pScript) pScript->unload(); + if (cmd.pScript) cmd.pScript->unload(); } + InstrumentChangeCommand.SwitchConfig(); // switch back to original one } /**