/*************************************************************************** * * * LinuxSampler - modular, streaming capable sampler * * * * 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-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 * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * * MA 02111-1307 USA * ***************************************************************************/ #ifndef __LS_ENGINECHANNELBASE_H__ #define __LS_ENGINECHANNELBASE_H__ #include "AbstractEngineChannel.h" #include "common/MidiKeyboardManager.h" #include "common/Voice.h" #include "../common/ResourceManager.h" namespace LinuxSampler { /// Command used by the instrument loader thread to /// request an instrument change on a channel. template class InstrumentChangeCmd { public: 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 class RegionPools { public: virtual Pool* GetRegionPool(int index) = 0; }; template 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::Iterator RTListRegionIterator; typedef typename MidiKeyboardManager::MidiKey MidiKey; virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE { return this; } virtual void HandBack(I* Instrument) { ResourceManager* mgr = dynamic_cast*>(pEngine->GetInstrumentManager()); mgr->HandBack(Instrument, this); } virtual void ClearRegionsInUse() { { InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear(); cmd.bChangeInstrument = false; } { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear(); cmd.bChangeInstrument = false; } } virtual void ResetRegionsInUse(Pool* pRegionPool[]) { DeleteRegionsInUse(); AllocateRegionsInUse(pRegionPool); } 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; } cmd.bChangeInstrument = false; } { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); if (cmd.pRegionsInUse) { if (cmd.pRegionsInUse != previous) delete cmd.pRegionsInUse; cmd.pRegionsInUse = NULL; } cmd.bChangeInstrument = false; } } virtual void AllocateRegionsInUse(Pool* pRegionPool[]) { { InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); cmd.pRegionsInUse = new RTList(pRegionPool[0]); cmd.bChangeInstrument = false; } { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); cmd.pRegionsInUse = new RTList(pRegionPool[1]); cmd.bChangeInstrument = false; } } virtual void Connect(AudioOutputDevice* pAudioOut) { if (pEngine) { if (pEngine->pAudioOutputDevice == pAudioOut) return; DisconnectAudioOutputDevice(); } AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut); { 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 // twice, as it is double buffered) { InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); cmd.pRegionsInUse = new RTList(pRegionPool->GetRegionPool(0)); cmd.pInstrument = 0; cmd.bChangeInstrument = false; } { InstrumentChangeCmd& cmd = InstrumentChangeCommand.SwitchConfig(); cmd.pRegionsInUse = new RTList(pRegionPool->GetRegionPool(1)); cmd.pInstrument = 0; cmd.bChangeInstrument = false; } if (pInstrument != NULL) { pInstrument = NULL; InstrumentStat = -1; InstrumentIdx = -1; InstrumentIdxName = ""; InstrumentFile = ""; bStatusChanged = true; } NotePool* pNotePool = dynamic_cast*>(pEngine); MidiKeyboardManager::AllocateActiveNotesLists( pNotePool->GetNotePool(), pNotePool->GetVoicePool() ); MidiKeyboardManager::AllocateEventsLists(pEngine->pEventPool); AudioDeviceChannelLeft = 0; AudioDeviceChannelRight = 1; if (fxSends.empty()) { // render directly into the AudioDevice's output buffers pChannelLeft = pAudioOut->Channel(AudioDeviceChannelLeft); pChannelRight = pAudioOut->Channel(AudioDeviceChannelRight); } else { // use local buffers for rendering and copy later // ensure the local buffers have the correct size if (pChannelLeft) delete pChannelLeft; if (pChannelRight) delete pChannelRight; pChannelLeft = new AudioChannel(0, pAudioOut->MaxSamplesPerCycle()); pChannelRight = new AudioChannel(1, pAudioOut->MaxSamplesPerCycle()); } if (pEngine->EngineDisabled.GetUnsafe()) pEngine->Enable(); MidiInputPort::AddSysexListener(pEngine); } virtual void DisconnectAudioOutputDevice() { if (pEngine) { // if clause to prevent disconnect loops 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) { // release the currently loaded instrument HandBack(cmd.pInstrument); } if (pEvents) { delete pEvents; pEvents = NULL; } if (delayedEvents.pList) { delete delayedEvents.pList; delayedEvents.pList = NULL; } MidiKeyboardManager::DeleteActiveNotesLists(); MidiKeyboardManager::DeleteEventsLists(); DeleteGroupEventLists(); AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice; { LockGuard lock(EngineMutex); pEngine = NULL; } AbstractEngine::FreeEngine(this, oldAudioDevice); AudioDeviceChannelLeft = -1; AudioDeviceChannelRight = -1; if (!fxSends.empty()) { // free the local rendering buffers if (pChannelLeft) delete pChannelLeft; if (pChannelRight) delete pChannelRight; } pChannelLeft = NULL; pChannelRight = NULL; } } class ClearEventListsHandler : public MidiKeyboardManager::VoiceHandlerBase { public: virtual bool Process(MidiKey* pMidiKey) { pMidiKey->pEvents->clear(); return false; } }; /** * 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 // (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' /** * Will be called by the InstrumentResourceManager when the instrument * 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) OVERRIDE { dmsg(3,("EngineChannelBase: Received instrument update message.\n")); if (pEngine) pEngine->DisableAndLock(); ResetInternal(false/*don't reset engine*/); this->pInstrument = NULL; } /** * 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) 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 } /** * Will be called by the InstrumentResourceManager on progress changes * while loading or realoading an instrument for this EngineChannel. * * @param fProgress - current progress as value between 0.0 and 1.0 */ virtual void OnResourceProgress(float fProgress) OVERRIDE { this->InstrumentStat = int(fProgress * 100.0f); dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat)); bStatusChanged = true; // status of engine has changed, so set notify flag } void RenderActiveVoices(uint Samples) { RenderVoicesHandler handler(this, Samples); this->ProcessActiveVoices(&handler); SetVoiceCount(handler.VoiceCount); 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; } RTList* pRegionsInUse; ///< temporary pointer into the instrument change command, used by the audio thread I* pInstrument; template friend class EngineBase; protected: EngineChannelBase() : MidiKeyboardManager(this), InstrumentChangeCommandReader(InstrumentChangeCommand) { pInstrument = NULL; // reset the instrument change command struct (need to be done // twice, as it is double buffered) { 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() { 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; class RenderVoicesHandler : public MidiKeyboardManager::VoiceHandlerBase { public: uint Samples; uint VoiceCount; uint StreamCount; EngineChannelBase* pChannel; RenderVoicesHandler(EngineChannelBase* channel, uint samples) : pChannel(channel), Samples(samples), VoiceCount(0), StreamCount(0) { } virtual void Process(RTListVoiceIterator& itVoice) { // now render current voice itVoice->Render(Samples); if (itVoice->IsActive()) { // still active if (!itVoice->Orphan) { *(pChannel->pRegionsInUse->allocAppend()) = itVoice->GetRegion(); } VoiceCount++; if (itVoice->PlaybackState == Voice::playback_state_disk) { if ((itVoice->DiskStreamRef).State != Stream::state_unused) StreamCount++; } } else { // voice reached end, is now inactive itVoice->VoiceFreed(); pChannel->FreeVoice(itVoice); // remove voice from the list of active voices } } }; typedef typename SynchronizedConfig >::Reader SyncConfInstrChangeCmdReader; SynchronizedConfig > InstrumentChangeCommand; SyncConfInstrChangeCmdReader InstrumentChangeCommandReader; /** This method is not thread safe! */ virtual void ResetInternal(bool bResetEngine) OVERRIDE { AbstractEngineChannel::ResetInternal(bResetEngine); MidiKeyboardManager::Reset(); } virtual void ResetControllers() { AbstractEngineChannel::ResetControllers(); MidiKeyboardManager::SustainPedal = false; MidiKeyboardManager::SostenutoPedal = false; } /** * 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 * @returns the resulting instrument change command after the * command switch, containing the old instrument and * the dimregions it is using */ InstrumentChangeCmd& ChangeInstrument(I* pInstrument) { InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate(); cmd.pInstrument = pInstrument; cmd.bChangeInstrument = true; return InstrumentChangeCommand.SwitchConfig(); } virtual void ProcessKeySwitchChange(int key) = 0; }; } // namespace LinuxSampler #endif /* __LS_ENGINECHANNELBASE_H__ */