/***************************************************************************
* *
* LinuxSampler - modular, streaming capable sampler *
* *
* Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck *
* Copyright (C) 2005-2020 Christian Schoenebeck *
* Copyright (C) 2009-2012 Grigor Iliev *
* Copyright (C) 2012-2017 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:
/**
* 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());
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) OVERRIDE {
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() OVERRIDE {
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
}
/**
* 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 ProcessReleaseTriggerBySustain(RTList::Iterator& itEvent) OVERRIDE {
if (!pEngine) return;
pEngine->ProcessReleaseTriggerBySustain(this, itEvent);
}
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, int64_t 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() :
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) :
Samples(samples), VoiceCount(0), StreamCount(0), pChannel(channel) { }
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() OVERRIDE {
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
* @param patchVars - 'patch' variables being overridden by instrument
*/
void LoadInstrumentScript(const String& text,
const std::map& patchVars)
{
InstrumentChangeCmd& cmd = InstrumentChangeCommand.GetConfigForUpdate();
// load the new script
cmd.pScript->load(text, patchVars);
}
/**
* 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__ */