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 |
635 |
|
|
636 |
Event event = pEngine->pEventGenerator->CreateEvent(); |
Event event = pEngine->pEventGenerator->CreateEvent(); |
637 |
event.Type = Event::type_channel_pressure; |
event.Type = Event::type_channel_pressure; |
638 |
|
event.Param.ChannelPressure.Controller = CTRL_TABLE_IDX_AFTERTOUCH; // required for instrument scripts |
639 |
event.Param.ChannelPressure.Value = Value; |
event.Param.ChannelPressure.Value = Value; |
640 |
event.Param.ChannelPressure.Channel = MidiChannel; |
event.Param.ChannelPressure.Channel = MidiChannel; |
641 |
event.pEngineChannel = this; |
event.pEngineChannel = this; |
652 |
|
|
653 |
Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); |
Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); |
654 |
event.Type = Event::type_channel_pressure; |
event.Type = Event::type_channel_pressure; |
655 |
|
event.Param.ChannelPressure.Controller = CTRL_TABLE_IDX_AFTERTOUCH; // required for instrument scripts |
656 |
event.Param.ChannelPressure.Value = Value; |
event.Param.ChannelPressure.Value = Value; |
657 |
event.Param.ChannelPressure.Channel = MidiChannel; |
event.Param.ChannelPressure.Channel = MidiChannel; |
658 |
event.pEngineChannel = this; |
event.pEngineChannel = this; |
695 |
} |
} |
696 |
} |
} |
697 |
|
|
698 |
|
bool AbstractEngineChannel::applyTranspose(Event* event) { |
699 |
|
if (event->Type != Event::type_note_on && event->Type != Event::type_note_off) |
700 |
|
return true; // event OK (not a note event, nothing to do with it here) |
701 |
|
|
702 |
|
//HACK: we should better add the transpose value only to the most mandatory places (like for retrieving the region and calculating the tuning), because otherwise voices will unintendedly survive when changing transpose while playing |
703 |
|
const int k = event->Param.Note.Key + GlobalTranspose; |
704 |
|
if (k < 0 || k > 127) |
705 |
|
return false; // bad event, drop it |
706 |
|
|
707 |
|
event->Param.Note.Key = k; |
708 |
|
|
709 |
|
return true; // event OK |
710 |
|
} |
711 |
|
|
712 |
/** |
/** |
713 |
* Copy all events from the engine channel's input event queue buffer to |
* Copy all events from the engine channel's input event queue buffer to |
714 |
* the internal event list. This will be done at the beginning of each |
* the internal event list. This will be done at the beginning of each |
745 |
event.Param.Note.Key = devEvent.Arg1; |
event.Param.Note.Key = devEvent.Arg1; |
746 |
event.Param.Note.Velocity = devEvent.Arg2; |
event.Param.Note.Velocity = devEvent.Arg2; |
747 |
event.Param.Note.Channel = channel; |
event.Param.Note.Channel = channel; |
748 |
|
// apply transpose setting to (note on/off) event |
749 |
|
if (!applyTranspose(&event)) |
750 |
|
continue; // note value is out of range, so drop this event |
751 |
|
// assign a new note to this note-on event |
752 |
|
if (!pEngine->LaunchNewNote(this, &event)) |
753 |
|
continue; // failed launching new note, so drop this event |
754 |
break; |
break; |
755 |
case VirtualMidiDevice::EVENT_TYPE_NOTEOFF: |
case VirtualMidiDevice::EVENT_TYPE_NOTEOFF: |
756 |
event.Type = Event::type_note_off; |
event.Type = Event::type_note_off; |
757 |
event.Param.Note.Key = devEvent.Arg1; |
event.Param.Note.Key = devEvent.Arg1; |
758 |
event.Param.Note.Velocity = devEvent.Arg2; |
event.Param.Note.Velocity = devEvent.Arg2; |
759 |
event.Param.Note.Channel = channel; |
event.Param.Note.Channel = channel; |
760 |
|
if (!applyTranspose(&event)) |
761 |
|
continue; // note value is out of range, so drop this event |
762 |
break; |
break; |
763 |
case VirtualMidiDevice::EVENT_TYPE_CC: |
case VirtualMidiDevice::EVENT_TYPE_CC: |
764 |
switch (devEvent.Arg1) { |
switch (devEvent.Arg1) { |
814 |
pEvent->ResetFragmentPos(); |
pEvent->ResetFragmentPos(); |
815 |
break; |
break; |
816 |
} |
} |
|
// copy event to internal event list |
|
817 |
if (pEvents->poolIsEmpty()) { |
if (pEvents->poolIsEmpty()) { |
818 |
dmsg(1,("Event pool emtpy!\n")); |
dmsg(1,("Event pool emtpy!\n")); |
819 |
break; |
break; |
820 |
} |
} |
821 |
|
// apply transpose setting to (note on/off) event |
822 |
|
if (!applyTranspose(pEvent)) |
823 |
|
continue; // it's a note event which has a note value out of range, so drop this event |
824 |
|
// assign a new note to this event (if its a note-on event) |
825 |
|
if (pEvent->Type == Event::type_note_on) |
826 |
|
if (!pEngine->LaunchNewNote(this, pEvent)) |
827 |
|
continue; // failed launching new note, so drop this event |
828 |
|
// copy event to internal event list |
829 |
*pEvents->allocAppend() = *pEvent; |
*pEvents->allocAppend() = *pEvent; |
830 |
} |
} |
831 |
eventQueueReader.free(); // free all copied events from input queue |
eventQueueReader.free(); // free all copied events from input queue |
832 |
} |
} |
833 |
|
|
834 |
/** |
/** |
835 |
* Called by real-time instrument script functions to schedule a new event |
* Called by real-time instrument script functions to schedule a new event |
836 |
* somewhere in future. |
* @a delay microseconds in future. |
837 |
|
* |
838 |
|
* @b IMPORTANT: for the supplied @a delay to be scheduled correctly, the |
839 |
|
* passed @a pEvent must be assigned a valid fragment time within the |
840 |
|
* current audio fragment boundaries. That fragment time will be used by |
841 |
|
* this method as basis for interpreting what "now" acutally is, and thus |
842 |
|
* it will be used as basis for calculating the precise scheduling time |
843 |
|
* for @a delay. The easiest way to achieve this is by copying a recent |
844 |
|
* event which happened within the current audio fragment cycle: i.e. the |
845 |
|
* original event which caused calling this method here. |
846 |
|
* |
847 |
|
* @param pEvent - event to be scheduled in future (event data will be copied) |
848 |
|
* @param delay - amount of microseconds in future (from now) when event shall be processed |
849 |
|
* @returns unique event ID of scheduled new event, or NULL on error |
850 |
*/ |
*/ |
851 |
void AbstractEngineChannel::ScheduleEvent(const Event* pEvent, int delay) { //TODO: delay not implemented yet |
event_id_t AbstractEngineChannel::ScheduleEventMicroSec(const Event* pEvent, int delay) { |
852 |
// 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 |
|
853 |
RTList<Event>::Iterator itEvent = pEvents->allocAppend(); |
RTList<Event>::Iterator itEvent = pEvents->allocAppend(); |
854 |
if (itEvent) *itEvent = *pEvent; // copy event |
if (!itEvent) { |
855 |
|
dmsg(1,("AbstractEngineChannel::ScheduleEventMicroSec(): Event pool emtpy!\n")); |
856 |
|
return 0; |
857 |
|
} |
858 |
|
RTList<ScheduledEvent>::Iterator itNode = delayedEvents.schedulerNodes.allocAppend(); |
859 |
|
if (!itNode) { // scheduler node pool empty ... |
860 |
|
dmsg(1,("AbstractEngineChannel::ScheduleEventMicroSec(): ScheduledEvent pool empty!\n")); |
861 |
|
pEvents->free(itEvent); |
862 |
|
return 0; |
863 |
|
} |
864 |
|
// copy passed event |
865 |
|
*itEvent = *pEvent; |
866 |
|
// move copied event to list of delayed events |
867 |
|
itEvent = itEvent.moveToEndOf(delayedEvents.pList); |
868 |
|
// connect scheduler node with the copied event |
869 |
|
itNode->itEvent = itEvent; |
870 |
|
// add entry to time sorted scheduler queue for copied event |
871 |
|
pEngine->pEventGenerator->scheduleAheadMicroSec( |
872 |
|
delayedEvents.queue, *itNode, itEvent->FragmentPos(), delay |
873 |
|
); |
874 |
|
//dmsg(5,("ScheduledEvent queue size: %d\n", delayedEvents.queue.size())); |
875 |
|
return pEvents->getID(itEvent); |
876 |
|
} |
877 |
|
|
878 |
|
/** |
879 |
|
* Called by real-time instrument script functions to ignore the event |
880 |
|
* reflected by given event ID. The event will be freed immediately to its |
881 |
|
* pool and cannot be dereferenced by its old ID anymore. Even if its |
882 |
|
* allocated back from the Pool later on, it will have a different ID. |
883 |
|
* |
884 |
|
* @param id - unique ID of event to be dropped |
885 |
|
*/ |
886 |
|
void AbstractEngineChannel::IgnoreEvent(event_id_t id) { |
887 |
|
RTList<Event>::Iterator it = pEvents->fromID(id); |
888 |
|
if (it) pEvents->free(it); |
889 |
|
} |
890 |
|
|
891 |
|
/** @brief Drop the requested event. |
892 |
|
* |
893 |
|
* Called by real-time instrument script functions to ignore the event |
894 |
|
* reflected by the given event @a id. This method detects whether the |
895 |
|
* passed ID is actually a @c Note ID or a regular @c Event ID and act |
896 |
|
* accordingly. |
897 |
|
* |
898 |
|
* @param id - event id (from script scope) |
899 |
|
* @see ScriptID |
900 |
|
*/ |
901 |
|
void AbstractEngineChannel::IgnoreEventByScriptID(const ScriptID& id) { |
902 |
|
switch (id.type()) { |
903 |
|
case ScriptID::EVENT: |
904 |
|
IgnoreEvent( id.eventID() ); |
905 |
|
break; |
906 |
|
case ScriptID::NOTE: |
907 |
|
IgnoreNote( id.noteID() ); |
908 |
|
break; |
909 |
|
} |
910 |
|
} |
911 |
|
|
912 |
|
/** @brief Order resuming of script execution instance "now". |
913 |
|
* |
914 |
|
* Called by real-time instrument script function stop_wait() to resume a |
915 |
|
* script callback currently being suspended (i.e. due to a wait() script |
916 |
|
* function call). |
917 |
|
* |
918 |
|
* @param itCallback - suspended script callback to be resumed |
919 |
|
* @param now - current scheduler time to be "now" |
920 |
|
* @param forever - whether this particulare script callback should ignore |
921 |
|
* all subsequent wait*() script function calls |
922 |
|
*/ |
923 |
|
void AbstractEngineChannel::ScheduleResumeOfScriptCallback(RTList<ScriptEvent>::Iterator& itCallback, sched_time_t now, bool forever) { |
924 |
|
// ignore if invalid iterator was passed |
925 |
|
if (!itCallback) return; |
926 |
|
|
927 |
|
ScriptEvent* pCallback = &*itCallback; |
928 |
|
|
929 |
|
// mark this callback to ignore all subsequent built-in wait*() script function calls |
930 |
|
if (forever) pCallback->ignoreAllWaitCalls = true; |
931 |
|
|
932 |
|
// ignore if callback is not in the scheduler queue |
933 |
|
if (pCallback->currentSchedulerQueue() != &pScript->suspendedEvents) return; |
934 |
|
|
935 |
|
// ignore if callback is already scheduled to be resumed "now" |
936 |
|
if (pCallback->scheduleTime <= now) return; |
937 |
|
|
938 |
|
// take it out from the scheduler queue and re-insert callback |
939 |
|
// to schedule the script callback for resuming execution "now" |
940 |
|
pScript->suspendedEvents.erase(*pCallback); |
941 |
|
pCallback->scheduleTime = now + 1; |
942 |
|
pScript->suspendedEvents.insert(*pCallback); |
943 |
} |
} |
944 |
|
|
945 |
FxSend* AbstractEngineChannel::AddFxSend(uint8_t MidiCtrl, String Name) throw (Exception) { |
FxSend* AbstractEngineChannel::AddFxSend(uint8_t MidiCtrl, String Name) throw (Exception) { |