4 |
* * |
* * |
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-2013 Christian Schoenebeck and Grigor Iliev * |
8 |
* * |
* * |
9 |
* This program is free software; you can redistribute it and/or modify * |
* This program is free software; you can redistribute it and/or modify * |
10 |
* 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 * |
104 |
* @param Samples - number of sample points to be rendered |
* @param Samples - number of sample points to be rendered |
105 |
* @returns 0 on success |
* @returns 0 on success |
106 |
*/ |
*/ |
107 |
virtual int RenderAudio(uint Samples) { |
virtual int RenderAudio(uint Samples) OVERRIDE { |
108 |
dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); |
dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); |
109 |
|
|
110 |
// return if engine disabled |
// return if engine disabled |
143 |
} |
} |
144 |
} |
} |
145 |
} |
} |
146 |
|
|
147 |
|
// In case scale tuning has been changed, recalculate pitch for |
148 |
|
// all active voices. |
149 |
|
ProcessScaleTuningChange(); |
150 |
|
|
151 |
// reset internal voice counter (just for statistic of active voices) |
// reset internal voice counter (just for statistic of active voices) |
152 |
ActiveVoiceCountTemp = 0; |
ActiveVoiceCountTemp = 0; |
209 |
return 0; |
return 0; |
210 |
} |
} |
211 |
|
|
212 |
virtual int MaxVoices() { return pVoicePool->poolSize(); } |
virtual int MaxVoices() OVERRIDE { return pVoicePool->poolSize(); } |
213 |
|
|
214 |
virtual void SetMaxVoices(int iVoices) throw (Exception) { |
virtual void SetMaxVoices(int iVoices) throw (Exception) OVERRIDE { |
215 |
if (iVoices < 1) |
if (iVoices < 1) |
216 |
throw Exception("Maximum voices for an engine cannot be set lower than 1"); |
throw Exception("Maximum voices for an engine cannot be set lower than 1"); |
217 |
|
|
254 |
/** Called after the new max number of voices is set and before resuming the engine. */ |
/** Called after the new max number of voices is set and before resuming the engine. */ |
255 |
virtual void PostSetMaxVoices(int iVoices) { } |
virtual void PostSetMaxVoices(int iVoices) { } |
256 |
|
|
257 |
virtual uint DiskStreamCount() { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; } |
virtual uint DiskStreamCount() OVERRIDE { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; } |
258 |
virtual uint DiskStreamCountMax() { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; } |
virtual uint DiskStreamCountMax() OVERRIDE { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; } |
259 |
virtual int MaxDiskStreams() { return iMaxDiskStreams; } |
virtual int MaxDiskStreams() OVERRIDE { return iMaxDiskStreams; } |
260 |
|
|
261 |
virtual void SetMaxDiskStreams(int iStreams) throw (Exception) { |
virtual void SetMaxDiskStreams(int iStreams) throw (Exception) OVERRIDE { |
262 |
if (iStreams < 0) |
if (iStreams < 0) |
263 |
throw Exception("Maximum disk streams for an engine cannot be set lower than 0"); |
throw Exception("Maximum disk streams for an engine cannot be set lower than 0"); |
264 |
|
|
273 |
ResumeAll(); |
ResumeAll(); |
274 |
} |
} |
275 |
|
|
276 |
virtual String DiskStreamBufferFillBytes() { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; } |
virtual String DiskStreamBufferFillBytes() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; } |
277 |
virtual String DiskStreamBufferFillPercentage() { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; } |
virtual String DiskStreamBufferFillPercentage() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; } |
278 |
virtual InstrumentManager* GetInstrumentManager() { return &instruments; } |
virtual InstrumentManager* GetInstrumentManager() OVERRIDE { return &instruments; } |
279 |
|
|
280 |
/** |
/** |
281 |
* Connect this engine instance with the given audio output device. |
* Connect this engine instance with the given audio output device. |
286 |
* |
* |
287 |
* @param pAudioOut - audio output device to connect to |
* @param pAudioOut - audio output device to connect to |
288 |
*/ |
*/ |
289 |
virtual void Connect(AudioOutputDevice* pAudioOut) { |
virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE { |
290 |
// caution: don't ignore if connecting to the same device here, |
// caution: don't ignore if connecting to the same device here, |
291 |
// because otherwise SetMaxDiskStreams() implementation won't work anymore! |
// because otherwise SetMaxDiskStreams() implementation won't work anymore! |
292 |
|
|
372 |
pDedicatedVoiceChannelLeft = new AudioChannel(0, MaxSamplesPerCycle); |
pDedicatedVoiceChannelLeft = new AudioChannel(0, MaxSamplesPerCycle); |
373 |
pDedicatedVoiceChannelRight = new AudioChannel(1, MaxSamplesPerCycle); |
pDedicatedVoiceChannelRight = new AudioChannel(1, MaxSamplesPerCycle); |
374 |
} |
} |
375 |
|
|
376 |
|
// Implementattion for abstract method derived from Engine. |
377 |
|
virtual void ReconnectAudioOutputDevice() OVERRIDE { |
378 |
|
SuspendAll(); |
379 |
|
if (pAudioOutputDevice) Connect(pAudioOutputDevice); |
380 |
|
ResumeAll(); |
381 |
|
} |
382 |
|
|
383 |
/** |
/** |
384 |
* Similar to @c Disable() but this method additionally kills all voices |
* Similar to @c Disable() but this method additionally kills all voices |
433 |
*/ |
*/ |
434 |
virtual void Suspend(RR* pRegion) { |
virtual void Suspend(RR* pRegion) { |
435 |
dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion)); |
dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion)); |
436 |
SuspendedRegionsMutex.Lock(); |
{ |
437 |
SuspensionChangeOngoing.Set(true); |
LockGuard lock(SuspendedRegionsMutex); |
438 |
pPendingRegionSuspension = pRegion; |
SuspensionChangeOngoing.Set(true); |
439 |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
pPendingRegionSuspension = pRegion; |
440 |
SuspendedRegionsMutex.Unlock(); |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
441 |
|
} |
442 |
dmsg(2,("EngineBase: Region %x suspended.",pRegion)); |
dmsg(2,("EngineBase: Region %x suspended.",pRegion)); |
443 |
} |
} |
444 |
|
|
450 |
*/ |
*/ |
451 |
virtual void Resume(RR* pRegion) { |
virtual void Resume(RR* pRegion) { |
452 |
dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion)); |
dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion)); |
453 |
SuspendedRegionsMutex.Lock(); |
{ |
454 |
SuspensionChangeOngoing.Set(true); |
LockGuard lock(SuspendedRegionsMutex); |
455 |
pPendingRegionResumption = pRegion; |
SuspensionChangeOngoing.Set(true); |
456 |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
pPendingRegionResumption = pRegion; |
457 |
SuspendedRegionsMutex.Unlock(); |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
458 |
|
} |
459 |
dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); |
dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); |
460 |
} |
} |
461 |
|
|
578 |
public: |
public: |
579 |
int PendingStreamDeletions; |
int PendingStreamDeletions; |
580 |
RR* pPendingRegionSuspension; |
RR* pPendingRegionSuspension; |
581 |
|
|
582 |
SuspensionVoiceHandler(RR* pPendingRegionSuspension) { |
SuspensionVoiceHandler(RR* pPendingRegionSuspension) { |
583 |
PendingStreamDeletions = 0; |
PendingStreamDeletions = 0; |
584 |
this->pPendingRegionSuspension = pPendingRegionSuspension; |
this->pPendingRegionSuspension = pPendingRegionSuspension; |
585 |
} |
} |
586 |
|
|
587 |
virtual bool Process(MidiKey* pMidiKey) { |
virtual bool Process(MidiKey* pMidiKey) OVERRIDE { |
588 |
VoiceIterator itVoice = pMidiKey->pActiveVoices->first(); |
VoiceIterator itVoice = pMidiKey->pActiveVoices->first(); |
589 |
// if current key is not associated with this region, skip this key |
// if current key is not associated with this region, skip this key |
590 |
if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false; |
if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false; |
592 |
return true; |
return true; |
593 |
} |
} |
594 |
|
|
595 |
virtual void Process(VoiceIterator& itVoice) { |
virtual void Process(VoiceIterator& itVoice) OVERRIDE { |
596 |
// request a notification from disk thread side for stream deletion |
// request a notification from disk thread side for stream deletion |
597 |
const Stream::Handle hStream = itVoice->KillImmediately(true); |
const Stream::Handle hStream = itVoice->KillImmediately(true); |
598 |
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
631 |
AbstractEngineChannel* pChannel = static_cast<AbstractEngineChannel*>(pEngineChannel); |
AbstractEngineChannel* pChannel = static_cast<AbstractEngineChannel*>(pEngineChannel); |
632 |
pChannel->ImportEvents(Samples); |
pChannel->ImportEvents(Samples); |
633 |
|
|
634 |
// process events |
// if a valid real-time instrument script is loaded, pre-process |
635 |
|
// the event list by running the script now, since the script |
636 |
|
// might filter events or add new ones for this cycle |
637 |
|
if (pChannel->script.bHasValidScript) { |
638 |
|
// resume any suspended script executions still hanging |
639 |
|
// around of previous audio fragment cycles |
640 |
|
for (RTList<ScriptEvent>::Iterator itEvent = pChannel->pScriptEvents->first(), |
641 |
|
end = pChannel->pScriptEvents->end(); itEvent != end; ++itEvent) |
642 |
|
{ |
643 |
|
ResumeScriptEvent(pChannel, itEvent); //TODO: implement support for actual suspension time (i.e. passed to a script's wait() function call) |
644 |
|
} |
645 |
|
|
646 |
|
// spawn new script executions for the new MIDI events of |
647 |
|
// this audio fragment cycle |
648 |
|
for (RTList<Event>::Iterator itEvent = pChannel->pEvents->first(), |
649 |
|
end = pChannel->pEvents->end(); itEvent != end; ++itEvent) |
650 |
|
{ |
651 |
|
switch (itEvent->Type) { |
652 |
|
case Event::type_note_on: |
653 |
|
if (pChannel->script.handlerNote) |
654 |
|
ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerNote); |
655 |
|
break; |
656 |
|
case Event::type_note_off: |
657 |
|
if (pChannel->script.handlerRelease) |
658 |
|
ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerRelease); |
659 |
|
break; |
660 |
|
case Event::type_control_change: |
661 |
|
case Event::type_channel_pressure: |
662 |
|
case Event::type_pitchbend: |
663 |
|
if (pChannel->script.handlerController) |
664 |
|
ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerController); |
665 |
|
break; |
666 |
|
case Event::type_note_pressure: |
667 |
|
//TODO: ... |
668 |
|
break; |
669 |
|
} |
670 |
|
} |
671 |
|
} |
672 |
|
|
673 |
|
// now process all events regularly |
674 |
{ |
{ |
675 |
RTList<Event>::Iterator itEvent = pChannel->pEvents->first(); |
RTList<Event>::Iterator itEvent = pChannel->pEvents->first(); |
676 |
RTList<Event>::Iterator end = pChannel->pEvents->end(); |
RTList<Event>::Iterator end = pChannel->pEvents->end(); |
688 |
dmsg(5,("Engine: MIDI CC received\n")); |
dmsg(5,("Engine: MIDI CC received\n")); |
689 |
ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); |
ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); |
690 |
break; |
break; |
691 |
|
case Event::type_channel_pressure: |
692 |
|
dmsg(5,("Engine: MIDI Chan. Pressure received\n")); |
693 |
|
ProcessChannelPressure((EngineChannel*)itEvent->pEngineChannel, itEvent); |
694 |
|
break; |
695 |
|
case Event::type_note_pressure: |
696 |
|
dmsg(5,("Engine: MIDI Note Pressure received\n")); |
697 |
|
ProcessPolyphonicKeyPressure((EngineChannel*)itEvent->pEngineChannel, itEvent); |
698 |
|
break; |
699 |
case Event::type_pitchbend: |
case Event::type_pitchbend: |
700 |
dmsg(5,("Engine: Pitchbend received\n")); |
dmsg(5,("Engine: Pitchbend received\n")); |
701 |
ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent); |
ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent); |
712 |
pLastStolenChannel = NULL; |
pLastStolenChannel = NULL; |
713 |
} |
} |
714 |
|
|
715 |
|
/** @brief Call instrument script's event handler for this event. |
716 |
|
* |
717 |
|
* Causes a new execution instance of the currently loaded real-time |
718 |
|
* instrument script's event handler (callback) to be spawned for |
719 |
|
* the given MIDI event. |
720 |
|
* |
721 |
|
* @param pChannel - engine channel on which the MIDI event occured |
722 |
|
* @param itEvent - MIDI event that causes this new script execution |
723 |
|
* @param pEventHandler - script's event handler to be executed |
724 |
|
*/ |
725 |
|
void ProcessEventByScript(AbstractEngineChannel* pChannel, RTList<Event>::Iterator& itEvent, VMEventHandler* pEventHandler) { |
726 |
|
RTList<ScriptEvent>::Iterator itScriptEvent = |
727 |
|
pChannel->pScriptEvents->allocAppend(); |
728 |
|
|
729 |
|
if (!itScriptEvent) return; // no free script event left for execution |
730 |
|
|
731 |
|
// fill the list of script handlers to be executed by this event |
732 |
|
int i = 0; |
733 |
|
if (pChannel->script.handlerInit) |
734 |
|
itScriptEvent->handlers[i++] = pChannel->script.handlerInit; |
735 |
|
itScriptEvent->handlers[i++] = pEventHandler; // actual event handler (i.e. note, controller) |
736 |
|
itScriptEvent->handlers[i] = NULL; // NULL termination of list |
737 |
|
|
738 |
|
// initialize/reset other members |
739 |
|
itScriptEvent->cause = *itEvent; |
740 |
|
itScriptEvent->currentHandler = 0; |
741 |
|
itScriptEvent->executionSlices = 0; |
742 |
|
|
743 |
|
// run script handler(s) |
744 |
|
VMExecStatus_t res = pScriptVM->exec( |
745 |
|
pChannel->script.parserContext, &*itScriptEvent |
746 |
|
); |
747 |
|
|
748 |
|
// in case the script was suspended, keep it on the allocated |
749 |
|
// ScriptEvent list to be continued on the next audio cycle, |
750 |
|
// otherwise if execution has been finished, free it for a new |
751 |
|
// future script event to be triggered from start |
752 |
|
if (!(res & VM_EXEC_SUSPENDED)) |
753 |
|
pChannel->pScriptEvents->free(itScriptEvent); |
754 |
|
} |
755 |
|
|
756 |
|
/** @brief Resume execution of instrument script. |
757 |
|
* |
758 |
|
* Will be called to resume execution of a real-time instrument |
759 |
|
* script event which has been suspended in a previous audio |
760 |
|
* fragment cycle. |
761 |
|
* |
762 |
|
* Script execution might be suspended for various reasons. Usually |
763 |
|
* a script will be suspended if the script called the built-in |
764 |
|
* "wait()" function, but it might also be suspended automatically |
765 |
|
* if the script took too much execution time in an audio fragment |
766 |
|
* cycle. So in the latter case automatic suspension is performed in |
767 |
|
* order to avoid harm for the sampler's overall real-time |
768 |
|
* requirements. |
769 |
|
* |
770 |
|
* @param pChannel - engine channel this script is running for |
771 |
|
* @param itScriptEvent - script execution that shall be resumed |
772 |
|
*/ |
773 |
|
void ResumeScriptEvent(AbstractEngineChannel* pChannel, RTList<ScriptEvent>::Iterator& itScriptEvent) { |
774 |
|
// run script |
775 |
|
VMExecStatus_t res = pScriptVM->exec( |
776 |
|
pChannel->script.parserContext, &*itScriptEvent |
777 |
|
); |
778 |
|
// in case the script was again suspended, keep it on the allocated |
779 |
|
// ScriptEvent list to be continued on the next audio cycle, |
780 |
|
// otherwise if execution has been finished, free it for a new |
781 |
|
// future script event to be triggered from start |
782 |
|
if (!(res & VM_EXEC_SUSPENDED)) |
783 |
|
pChannel->pScriptEvents->free(itScriptEvent); |
784 |
|
} |
785 |
|
|
786 |
/** |
/** |
787 |
* Will be called by LaunchVoice() method in case there are no free |
* Will be called by LaunchVoice() method in case there are no free |
788 |
* voices left. This method will select and kill one old voice for |
* voices left. This method will select and kill one old voice for |
1450 |
* control and status variables. This method is protected by a mutex. |
* control and status variables. This method is protected by a mutex. |
1451 |
*/ |
*/ |
1452 |
virtual void ResetInternal() { |
virtual void ResetInternal() { |
1453 |
ResetInternalMutex.Lock(); |
LockGuard lock(ResetInternalMutex); |
1454 |
|
|
1455 |
// make sure that the engine does not get any sysex messages |
// make sure that the engine does not get any sysex messages |
1456 |
// while it's reseting |
// while it's reseting |
1479 |
pEventQueue->init(); |
pEventQueue->init(); |
1480 |
pSysexBuffer->init(); |
pSysexBuffer->init(); |
1481 |
if (sysexDisabled) MidiInputPort::AddSysexListener(this); |
if (sysexDisabled) MidiInputPort::AddSysexListener(this); |
|
ResetInternalMutex.Unlock(); |
|
1482 |
} |
} |
1483 |
|
|
1484 |
/** |
/** |
1574 |
|
|
1575 |
return -1; |
return -1; |
1576 |
} |
} |
1577 |
|
|
1578 |
|
/** |
1579 |
|
* Checks whether scale tuning setting has been changed since last |
1580 |
|
* time this method was called, if yes, it recalculates the pitch |
1581 |
|
* for all active voices. |
1582 |
|
*/ |
1583 |
|
void ProcessScaleTuningChange() { |
1584 |
|
const bool changed = ScaleTuningChanged.readAndReset(); |
1585 |
|
if (!changed) return; |
1586 |
|
|
1587 |
|
for (int i = 0; i < engineChannels.size(); i++) { |
1588 |
|
EngineChannelBase<V, R, I>* channel = |
1589 |
|
static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]); |
1590 |
|
channel->OnScaleTuningChanged(); |
1591 |
|
} |
1592 |
|
} |
1593 |
|
|
1594 |
private: |
private: |
1595 |
Pool<V>* pVoicePool; ///< Contains all voices that can be activated. |
Pool<V>* pVoicePool; ///< Contains all voices that can be activated. |