5 |
* Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * |
* Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * |
6 |
* Copyright (C) 2005-2008 Christian Schoenebeck * |
* Copyright (C) 2005-2008 Christian Schoenebeck * |
7 |
* Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * |
* Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * |
8 |
* Copyright (C) 2013-2014 Christian Schoenebeck and Andreas Persson * |
* Copyright (C) 2012-2016 Christian Schoenebeck and Andreas Persson * |
9 |
* * |
* * |
10 |
* This program is free software; you can redistribute it and/or modify * |
* This program is free software; you can redistribute it and/or modify * |
11 |
* it under the terms of the GNU General Public License as published by * |
* it under the terms of the GNU General Public License as published by * |
35 |
{ |
{ |
36 |
pEngine = NULL; |
pEngine = NULL; |
37 |
pEvents = NULL; // we allocate when we retrieve the right Engine object |
pEvents = NULL; // we allocate when we retrieve the right Engine object |
38 |
|
delayedEvents.pList = NULL; |
39 |
pEventQueue = new RingBuffer<Event,false>(CONFIG_MAX_EVENTS_PER_FRAGMENT, 0); |
pEventQueue = new RingBuffer<Event,false>(CONFIG_MAX_EVENTS_PER_FRAGMENT, 0); |
40 |
InstrumentIdx = -1; |
InstrumentIdx = -1; |
41 |
InstrumentStat = -1; |
InstrumentStat = -1; |
47 |
ResetControllers(); |
ResetControllers(); |
48 |
PortamentoMode = false; |
PortamentoMode = false; |
49 |
PortamentoTime = CONFIG_PORTAMENTO_TIME_DEFAULT; |
PortamentoTime = CONFIG_PORTAMENTO_TIME_DEFAULT; |
50 |
pScriptEvents = NULL; |
pScript = NULL; |
51 |
} |
} |
52 |
|
|
53 |
AbstractEngineChannel::~AbstractEngineChannel() { |
AbstractEngineChannel::~AbstractEngineChannel() { |
|
unloadCurrentInstrumentScript(); |
|
|
if (pScriptEvents) delete pScriptEvents; |
|
54 |
delete pEventQueue; |
delete pEventQueue; |
55 |
DeleteGroupEventLists(); |
DeleteGroupEventLists(); |
56 |
RemoveAllFxSends(); |
RemoveAllFxSends(); |
102 |
|
|
103 |
void AbstractEngineChannel::Reset() { |
void AbstractEngineChannel::Reset() { |
104 |
if (pEngine) pEngine->DisableAndLock(); |
if (pEngine) pEngine->DisableAndLock(); |
105 |
ResetInternal(); |
ResetInternal(false/*don't reset engine*/); |
106 |
ResetControllers(); |
ResetControllers(); |
107 |
if (pEngine) { |
if (pEngine) { |
108 |
pEngine->Enable(); |
pEngine->Enable(); |
130 |
/** |
/** |
131 |
* This method is not thread safe! |
* This method is not thread safe! |
132 |
*/ |
*/ |
133 |
void AbstractEngineChannel::ResetInternal() { |
void AbstractEngineChannel::ResetInternal(bool bResetEngine) { |
134 |
CurrentKeyDimension = 0; |
CurrentKeyDimension = 0; |
135 |
PortamentoPos = -1.0f; // no portamento active yet |
PortamentoPos = -1.0f; // no portamento active yet |
136 |
|
|
137 |
|
// delete all active instrument script events |
138 |
|
if (pScript) pScript->resetEvents(); |
139 |
|
|
140 |
|
// free all delayed MIDI events |
141 |
|
delayedEvents.clear(); |
142 |
|
|
143 |
// delete all input events |
// delete all input events |
144 |
pEventQueue->init(); |
pEventQueue->init(); |
145 |
|
|
146 |
if (pEngine) pEngine->ResetInternal(); |
if (bResetEngine && pEngine) pEngine->ResetInternal(); |
147 |
|
|
148 |
// status of engine channel has changed, so set notify flag |
// status of engine channel has changed, so set notify flag |
149 |
bStatusChanged = true; |
bStatusChanged = true; |
150 |
} |
} |
151 |
|
|
152 |
/** |
/** |
|
* Loads the real-time instrument script given by @a text on this engine |
|
|
* channel. A resource manager is used to allocate and share equivalent |
|
|
* scripts on multiple engine channels. |
|
|
* |
|
|
* @param text - source code of script |
|
|
*/ |
|
|
void AbstractEngineChannel::loadInstrumentScript(const String& text) { |
|
|
dmsg(1,("Loading real-time instrument script ... ")); |
|
|
|
|
|
// hand back old script reference and VM execution contexts |
|
|
// (if not done already) |
|
|
unloadCurrentInstrumentScript(); |
|
|
|
|
|
// get new script reference |
|
|
script.parserContext = pEngine->scripts.Borrow(text, this); |
|
|
if (!script.parserContext->errors().empty()) { |
|
|
std::vector<ParserIssue> errors = script.parserContext->errors(); |
|
|
std::cerr << "[ScriptVM] Could not load instrument script, there were " |
|
|
<< errors.size() << " parser errors:\n"; |
|
|
for (int i = 0; i < errors.size(); ++i) |
|
|
errors[i].dump(); |
|
|
return; // stop here if there were any parser errors |
|
|
} |
|
|
|
|
|
script.handlerInit = script.parserContext->eventHandlerByName("init"); |
|
|
script.handlerNote = script.parserContext->eventHandlerByName("note"); |
|
|
script.handlerRelease = script.parserContext->eventHandlerByName("release"); |
|
|
script.handlerController = script.parserContext->eventHandlerByName("controller"); |
|
|
script.bHasValidScript = |
|
|
script.handlerInit || script.handlerNote || script.handlerRelease || |
|
|
script.handlerController; |
|
|
|
|
|
// amount of script handlers each script event has to execute |
|
|
int handlerExecCount = 0; |
|
|
if (script.handlerInit) handlerExecCount++; // "init" handler is always executed before the actual event handler |
|
|
if (script.handlerNote || script.handlerRelease || script.handlerController) // only one of these are executed after "init" handler |
|
|
handlerExecCount++; |
|
|
|
|
|
// create script event pool (if it doesn't exist already) |
|
|
if (!pScriptEvents) |
|
|
pScriptEvents = new Pool<ScriptEvent>(CONFIG_MAX_EVENTS_PER_FRAGMENT); |
|
|
|
|
|
// create new VM execution contexts for new script |
|
|
while (!pScriptEvents->poolIsEmpty()) { |
|
|
RTList<ScriptEvent>::Iterator it = pScriptEvents->allocAppend(); |
|
|
it->execCtx = pEngine->pScriptVM->createExecContext( |
|
|
script.parserContext |
|
|
); |
|
|
it->handlers = new VMEventHandler*[handlerExecCount+1]; |
|
|
} |
|
|
pScriptEvents->clear(); |
|
|
|
|
|
dmsg(1,("Done\n")); |
|
|
} |
|
|
|
|
|
/** |
|
|
* Unloads the currently used real-time instrument script on this sampler |
|
|
* channel. A resource manager is used to share equivalent scripts among |
|
|
* multiple sampler channels, and to deallocate the parsed script once not |
|
|
* used on any engine channel anymore. |
|
|
*/ |
|
|
void AbstractEngineChannel::unloadCurrentInstrumentScript() { |
|
|
if (script.parserContext) |
|
|
dmsg(1,("Unloading current instrument script.")); |
|
|
|
|
|
// free allocated VM execution contexts |
|
|
if (pScriptEvents) { |
|
|
pScriptEvents->clear(); |
|
|
while (!pScriptEvents->poolIsEmpty()) { |
|
|
RTList<ScriptEvent>::Iterator it = pScriptEvents->allocAppend(); |
|
|
if (it->execCtx) { |
|
|
// free VM execution context object |
|
|
delete it->execCtx; |
|
|
it->execCtx = NULL; |
|
|
// free C array of handler pointers |
|
|
delete [] it->handlers; |
|
|
} |
|
|
} |
|
|
pScriptEvents->clear(); |
|
|
} |
|
|
// hand back VM representation of script |
|
|
if (script.parserContext) { |
|
|
pEngine->scripts.HandBack(script.parserContext, this); |
|
|
script.parserContext = NULL; |
|
|
script.handlerInit = NULL; |
|
|
script.handlerNote = NULL; |
|
|
script.handlerRelease = NULL; |
|
|
script.handlerController = NULL; |
|
|
} |
|
|
script.bHasValidScript = false; |
|
|
} |
|
|
|
|
|
/** |
|
153 |
* Implementation of virtual method from abstract EngineChannel interface. |
* Implementation of virtual method from abstract EngineChannel interface. |
154 |
* This method will periodically be polled (e.g. by the LSCP server) to |
* This method will periodically be polled (e.g. by the LSCP server) to |
155 |
* check if some engine channel parameter has changed since the last |
* check if some engine channel parameter has changed since the last |
643 |
|
|
644 |
Event event = pEngine->pEventGenerator->CreateEvent(); |
Event event = pEngine->pEventGenerator->CreateEvent(); |
645 |
event.Type = Event::type_channel_pressure; |
event.Type = Event::type_channel_pressure; |
646 |
|
event.Param.ChannelPressure.Controller = CTRL_TABLE_IDX_AFTERTOUCH; // required for instrument scripts |
647 |
event.Param.ChannelPressure.Value = Value; |
event.Param.ChannelPressure.Value = Value; |
648 |
event.Param.ChannelPressure.Channel = MidiChannel; |
event.Param.ChannelPressure.Channel = MidiChannel; |
649 |
memset(&event.Format, 0, sizeof(event.Format)); // init format speific stuff with zeroes |
memset(&event.Format, 0, sizeof(event.Format)); // init format speific stuff with zeroes |
661 |
|
|
662 |
Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); |
Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); |
663 |
event.Type = Event::type_channel_pressure; |
event.Type = Event::type_channel_pressure; |
664 |
|
event.Param.ChannelPressure.Controller = CTRL_TABLE_IDX_AFTERTOUCH; // required for instrument scripts |
665 |
event.Param.ChannelPressure.Value = Value; |
event.Param.ChannelPressure.Value = Value; |
666 |
event.Param.ChannelPressure.Channel = MidiChannel; |
event.Param.ChannelPressure.Channel = MidiChannel; |
667 |
memset(&event.Format, 0, sizeof(event.Format)); // init format speific stuff with zeroes |
memset(&event.Format, 0, sizeof(event.Format)); // init format speific stuff with zeroes |
778 |
<< devEvent.Type << "). This is a bug!"; |
<< devEvent.Type << "). This is a bug!"; |
779 |
continue; |
continue; |
780 |
} |
} |
781 |
|
memset(&event.Format, 0, sizeof(event.Format)); // init format specific stuff with zeroes |
782 |
event.pEngineChannel = this; |
event.pEngineChannel = this; |
783 |
// copy event to internal event list |
// copy event to internal event list |
784 |
if (pEvents->poolIsEmpty()) { |
if (pEvents->poolIsEmpty()) { |
817 |
|
|
818 |
/** |
/** |
819 |
* Called by real-time instrument script functions to schedule a new event |
* Called by real-time instrument script functions to schedule a new event |
820 |
* somewhere in future. |
* @a delay microseconds in future. |
821 |
* |
* |
822 |
* @returns unique event ID of scheduled new event |
* @b IMPORTANT: for the supplied @a delay to be scheduled correctly, the |
823 |
|
* passed @a pEvent must be assigned a valid fragment time within the |
824 |
|
* current audio fragment boundaries. That fragment time will be used by |
825 |
|
* this method as basis for interpreting what "now" acutally is, and thus |
826 |
|
* it will be used as basis for calculating the precise scheduling time |
827 |
|
* for @a delay. The easiest way to achieve this is by copying a recent |
828 |
|
* event which happened within the current audio fragment cycle: i.e. the |
829 |
|
* original event which caused calling this method here. |
830 |
|
* |
831 |
|
* @param pEvent - event to be scheduled in future (event data will be copied) |
832 |
|
* @param delay - amount of microseconds in future (from now) when event shall be processed |
833 |
|
* @returns unique event ID of scheduled new event, or a negative number on error |
834 |
*/ |
*/ |
835 |
int AbstractEngineChannel::ScheduleEvent(const Event* pEvent, int delay) { //TODO: delay not implemented yet |
int AbstractEngineChannel::ScheduleEventMicroSec(const Event* pEvent, int delay) { |
836 |
// since delay is not implemented yet, we simply add the new event |
dmsg(3,("AbstractEngineChannel::ScheduleEventMicroSec(Event.Type=%d,delay=%d)\n", pEvent->Type, delay)); |
|
// to the event list of the current audio fragmet cycle for now |
|
837 |
RTList<Event>::Iterator itEvent = pEvents->allocAppend(); |
RTList<Event>::Iterator itEvent = pEvents->allocAppend(); |
838 |
if (itEvent) *itEvent = *pEvent; // copy event |
if (!itEvent) { |
839 |
|
dmsg(1,("AbstractEngineChannel::ScheduleEventMicroSec(): Event pool emtpy!\n")); |
840 |
|
return -1; |
841 |
|
} |
842 |
|
RTList<ScheduledEvent>::Iterator itNode = delayedEvents.schedulerNodes.allocAppend(); |
843 |
|
if (!itNode) { // scheduler node pool empty ... |
844 |
|
dmsg(1,("AbstractEngineChannel::ScheduleEventMicroSec(): ScheduledEvent pool empty!\n")); |
845 |
|
pEvents->free(itEvent); |
846 |
|
return -1; |
847 |
|
} |
848 |
|
// copy passed event |
849 |
|
*itEvent = *pEvent; |
850 |
|
// move copied event to list of delayed events |
851 |
|
itEvent = itEvent.moveToEndOf(delayedEvents.pList); |
852 |
|
// connect scheduler node with the copied event |
853 |
|
itNode->itEvent = itEvent; |
854 |
|
// add entry to time sorted scheduler queue for copied event |
855 |
|
pEngine->pEventGenerator->scheduleAheadMicroSec( |
856 |
|
delayedEvents.queue, *itNode, itEvent->FragmentPos(), delay |
857 |
|
); |
858 |
|
//dmsg(5,("ScheduledEvent queue size: %d\n", delayedEvents.queue.size())); |
859 |
return pEvents->getID(itEvent); |
return pEvents->getID(itEvent); |
860 |
} |
} |
861 |
|
|