2 |
* * |
* * |
3 |
* LinuxSampler - modular, streaming capable sampler * |
* LinuxSampler - modular, streaming capable sampler * |
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-2007 Christian Schoenebeck * |
* Copyright (C) 2005-2008 Christian Schoenebeck * |
7 |
* * |
* * |
8 |
* This program is free software; you can redistribute it and/or modify * |
* This program is free software; you can redistribute it and/or modify * |
9 |
* 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 * |
29 |
|
|
30 |
#include "Engine.h" |
#include "Engine.h" |
31 |
|
|
32 |
|
#include "../../common/global_private.h" |
33 |
|
|
34 |
namespace LinuxSampler { namespace gig { |
namespace LinuxSampler { namespace gig { |
35 |
|
|
36 |
InstrumentResourceManager Engine::instruments; |
InstrumentResourceManager Engine::instruments; |
74 |
|
|
75 |
/** |
/** |
76 |
* Once an engine channel is disconnected from an audio output device, |
* Once an engine channel is disconnected from an audio output device, |
77 |
* it wil immediately call this method to unregister itself from the |
* it will immediately call this method to unregister itself from the |
78 |
* engine instance and if that engine instance is not used by any other |
* engine instance and if that engine instance is not used by any other |
79 |
* engine channel anymore, then that engine instance will be destroyed. |
* engine channel anymore, then that engine instance will be destroyed. |
80 |
* |
* |
108 |
pEventQueue = new RingBuffer<Event,false>(CONFIG_MAX_EVENTS_PER_FRAGMENT, 0); |
pEventQueue = new RingBuffer<Event,false>(CONFIG_MAX_EVENTS_PER_FRAGMENT, 0); |
109 |
pEventPool = new Pool<Event>(CONFIG_MAX_EVENTS_PER_FRAGMENT); |
pEventPool = new Pool<Event>(CONFIG_MAX_EVENTS_PER_FRAGMENT); |
110 |
pVoicePool = new Pool<Voice>(CONFIG_MAX_VOICES); |
pVoicePool = new Pool<Voice>(CONFIG_MAX_VOICES); |
111 |
pDimRegionsInUse = new ::gig::DimensionRegion*[CONFIG_MAX_VOICES + 1]; |
pDimRegionPool[0] = new Pool< ::gig::DimensionRegion*>(CONFIG_MAX_VOICES); |
112 |
|
pDimRegionPool[1] = new Pool< ::gig::DimensionRegion*>(CONFIG_MAX_VOICES); |
113 |
pVoiceStealingQueue = new RTList<Event>(pEventPool); |
pVoiceStealingQueue = new RTList<Event>(pEventPool); |
114 |
pGlobalEvents = new RTList<Event>(pEventPool); |
pGlobalEvents = new RTList<Event>(pEventPool); |
|
InstrumentChangeQueue = new RingBuffer<instrument_change_command_t,false>(1, 0); |
|
|
InstrumentChangeReplyQueue = new RingBuffer<instrument_change_reply_t,false>(1, 0); |
|
115 |
|
|
116 |
for (RTList<Voice>::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
for (RTList<Voice>::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
117 |
iterVoice->SetEngine(this); |
iterVoice->SetEngine(this); |
144 |
if (pVoiceStealingQueue) delete pVoiceStealingQueue; |
if (pVoiceStealingQueue) delete pVoiceStealingQueue; |
145 |
if (pSysexBuffer) delete pSysexBuffer; |
if (pSysexBuffer) delete pSysexBuffer; |
146 |
if (pGlobalEvents) delete pGlobalEvents; |
if (pGlobalEvents) delete pGlobalEvents; |
147 |
if (InstrumentChangeQueue) delete InstrumentChangeQueue; |
if (pDimRegionPool[0]) delete pDimRegionPool[0]; |
148 |
if (InstrumentChangeReplyQueue) delete InstrumentChangeReplyQueue; |
if (pDimRegionPool[1]) delete pDimRegionPool[1]; |
|
if (pDimRegionsInUse) delete[] pDimRegionsInUse; |
|
149 |
ResetSuspendedRegions(); |
ResetSuspendedRegions(); |
150 |
Unregister(); |
Unregister(); |
151 |
} |
} |
189 |
* engine afterwards by calling @c ResumeAll() later on! |
* engine afterwards by calling @c ResumeAll() later on! |
190 |
*/ |
*/ |
191 |
void Engine::SuspendAll() { |
void Engine::SuspendAll() { |
192 |
dmsg(1,("gig::Engine: Suspending all ...\n")); |
dmsg(2,("gig::Engine: Suspending all ...\n")); |
193 |
// stop the engine, so we can safely modify the engine's |
// stop the engine, so we can safely modify the engine's |
194 |
// data structures from this foreign thread |
// data structures from this foreign thread |
195 |
DisableAndLock(); |
DisableAndLock(); |
223 |
if (!iPendingStreamDeletions) break; |
if (!iPendingStreamDeletions) break; |
224 |
usleep(10000); // sleep for 10ms |
usleep(10000); // sleep for 10ms |
225 |
} |
} |
226 |
dmsg(1,("gig::Engine: Everything suspended.\n")); |
dmsg(2,("gig::Engine: Everything suspended.\n")); |
227 |
} |
} |
228 |
|
|
229 |
/** |
/** |
245 |
* @param pRegion - region the engine shall stop using |
* @param pRegion - region the engine shall stop using |
246 |
*/ |
*/ |
247 |
void Engine::Suspend(::gig::Region* pRegion) { |
void Engine::Suspend(::gig::Region* pRegion) { |
248 |
dmsg(1,("gig::Engine: Suspending Region %x ...\n",pRegion)); |
dmsg(2,("gig::Engine: Suspending Region %x ...\n",pRegion)); |
249 |
SuspendedRegionsMutex.Lock(); |
SuspendedRegionsMutex.Lock(); |
250 |
SuspensionChangeOngoing.Set(true); |
SuspensionChangeOngoing.Set(true); |
251 |
pPendingRegionSuspension = pRegion; |
pPendingRegionSuspension = pRegion; |
252 |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
253 |
SuspendedRegionsMutex.Unlock(); |
SuspendedRegionsMutex.Unlock(); |
254 |
dmsg(1,("gig::Engine: Region %x suspended.",pRegion)); |
dmsg(2,("gig::Engine: Region %x suspended.",pRegion)); |
255 |
} |
} |
256 |
|
|
257 |
/** |
/** |
261 |
* @param pRegion - region the engine shall be allowed to use again |
* @param pRegion - region the engine shall be allowed to use again |
262 |
*/ |
*/ |
263 |
void Engine::Resume(::gig::Region* pRegion) { |
void Engine::Resume(::gig::Region* pRegion) { |
264 |
dmsg(1,("gig::Engine: Resuming Region %x ...\n",pRegion)); |
dmsg(2,("gig::Engine: Resuming Region %x ...\n",pRegion)); |
265 |
SuspendedRegionsMutex.Lock(); |
SuspendedRegionsMutex.Lock(); |
266 |
SuspensionChangeOngoing.Set(true); |
SuspensionChangeOngoing.Set(true); |
267 |
pPendingRegionResumption = pRegion; |
pPendingRegionResumption = pRegion; |
268 |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
269 |
SuspendedRegionsMutex.Unlock(); |
SuspendedRegionsMutex.Unlock(); |
270 |
dmsg(1,("gig::Engine: Region %x resumed.\n",pRegion)); |
dmsg(2,("gig::Engine: Region %x resumed.\n",pRegion)); |
271 |
} |
} |
272 |
|
|
273 |
/** |
/** |
458 |
// free request slot for next caller (and to make sure that |
// free request slot for next caller (and to make sure that |
459 |
// we're not going to process the same request in the next cycle) |
// we're not going to process the same request in the next cycle) |
460 |
pPendingRegionSuspension = NULL; |
pPendingRegionSuspension = NULL; |
461 |
// if no disk stream deletions are pending, awaker other side, as |
// if no disk stream deletions are pending, awaken other side, as |
462 |
// we're done in this case |
// we're done in this case |
463 |
if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); |
if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); |
464 |
} |
} |
569 |
* @returns 0 on success |
* @returns 0 on success |
570 |
*/ |
*/ |
571 |
int Engine::RenderAudio(uint Samples) { |
int Engine::RenderAudio(uint Samples) { |
572 |
dmsg(7,("RenderAudio(Samples=%d)\n", Samples)); |
dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); |
573 |
|
|
574 |
// return if engine disabled |
// return if engine disabled |
575 |
if (EngineDisabled.Pop()) { |
if (EngineDisabled.Pop()) { |
611 |
ActiveVoiceCountTemp = 0; |
ActiveVoiceCountTemp = 0; |
612 |
|
|
613 |
// handle instrument change commands |
// handle instrument change commands |
614 |
instrument_change_command_t command; |
bool instrumentChanged = false; |
615 |
if (InstrumentChangeQueue->pop(&command) > 0) { |
for (int i = 0; i < engineChannels.size(); i++) { |
616 |
EngineChannel* pEngineChannel = command.pEngineChannel; |
EngineChannel* pEngineChannel = engineChannels[i]; |
|
pEngineChannel->pInstrument = command.pInstrument; |
|
|
|
|
|
//TODO: this is a lazy solution ATM and not safe in case somebody is currently editing the instrument we're currently switching to (we should store all suspended regions on instrument manager side and when switching to another instrument copy that list to the engine's local list of suspensions |
|
|
ResetSuspendedRegions(); |
|
617 |
|
|
618 |
// iterate through all active voices and mark their |
// as we're going to (carefully) write some status to the |
619 |
// dimension regions as "in use". The instrument resource |
// synchronized struct, we cast away the const |
620 |
// manager may delete all of the instrument except the |
EngineChannel::instrument_change_command_t& cmd = |
621 |
// dimension regions and samples that are in use. |
const_cast<EngineChannel::instrument_change_command_t&>(pEngineChannel->InstrumentChangeCommandReader.Lock()); |
622 |
int i = 0; |
|
623 |
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
pEngineChannel->pDimRegionsInUse = cmd.pDimRegionsInUse; |
624 |
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
pEngineChannel->pDimRegionsInUse->clear(); |
625 |
while (iuiKey != end) { // iterate through all active keys |
|
626 |
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
if (cmd.bChangeInstrument) { |
627 |
++iuiKey; |
// change instrument |
628 |
|
dmsg(5,("Engine: instrument change command received\n")); |
629 |
|
cmd.bChangeInstrument = false; |
630 |
|
pEngineChannel->pInstrument = cmd.pInstrument; |
631 |
|
instrumentChanged = true; |
632 |
|
|
633 |
|
// Iterate through all active voices and mark them as |
634 |
|
// "orphans", which means that the dimension regions |
635 |
|
// and samples they use should be released to the |
636 |
|
// instrument resource manager when the voices die. |
637 |
|
int i = 0; |
638 |
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
639 |
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
640 |
|
while (iuiKey != end) { // iterate through all active keys |
641 |
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
642 |
|
++iuiKey; |
643 |
|
|
644 |
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
645 |
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
646 |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
|
if (!itVoice->Orphan) { |
|
647 |
itVoice->Orphan = true; |
itVoice->Orphan = true; |
|
pDimRegionsInUse[i++] = itVoice->pDimRgn; |
|
648 |
} |
} |
649 |
} |
} |
650 |
} |
} |
651 |
pDimRegionsInUse[i] = 0; // end of list |
} |
652 |
|
if (instrumentChanged) { |
653 |
// send a reply to the calling thread, which is waiting |
//TODO: this is a lazy solution ATM and not safe in case somebody is currently editing the instrument we're currently switching to (we should store all suspended regions on instrument manager side and when switching to another instrument copy that list to the engine's local list of suspensions |
654 |
instrument_change_reply_t reply; |
ResetSuspendedRegions(); |
|
InstrumentChangeReplyQueue->push(&reply); |
|
655 |
} |
} |
656 |
|
|
657 |
// handle events on all engine channels |
// handle events on all engine channels |
694 |
// been deleted by the disk thread |
// been deleted by the disk thread |
695 |
if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); |
if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); |
696 |
|
|
697 |
|
for (int i = 0; i < engineChannels.size(); i++) { |
698 |
|
engineChannels[i]->InstrumentChangeCommandReader.Unlock(); |
699 |
|
} |
700 |
FrameTime += Samples; |
FrameTime += Samples; |
701 |
|
|
702 |
return 0; |
return 0; |
778 |
// now render current voice |
// now render current voice |
779 |
itVoice->Render(Samples); |
itVoice->Render(Samples); |
780 |
if (itVoice->IsActive()) { // still active |
if (itVoice->IsActive()) { // still active |
781 |
|
if (!itVoice->Orphan) { |
782 |
|
*(pEngineChannel->pDimRegionsInUse->allocAppend()) = itVoice->pDimRgn; |
783 |
|
} |
784 |
ActiveVoiceCountTemp++; |
ActiveVoiceCountTemp++; |
785 |
voiceCount++; |
voiceCount++; |
786 |
|
|
821 |
if (itNewVoice) { |
if (itNewVoice) { |
822 |
itNewVoice->Render(Samples); |
itNewVoice->Render(Samples); |
823 |
if (itNewVoice->IsActive()) { // still active |
if (itNewVoice->IsActive()) { // still active |
824 |
|
*(pEngineChannel->pDimRegionsInUse->allocAppend()) = itNewVoice->pDimRgn; |
825 |
ActiveVoiceCountTemp++; |
ActiveVoiceCountTemp++; |
826 |
pEngineChannel->SetVoiceCount(pEngineChannel->GetVoiceCount() + 1); |
pEngineChannel->SetVoiceCount(pEngineChannel->GetVoiceCount() + 1); |
827 |
|
|
1618 |
void Engine::ProcessControlChange(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itControlChangeEvent) { |
void Engine::ProcessControlChange(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itControlChangeEvent) { |
1619 |
dmsg(4,("Engine::ContinuousController cc=%d v=%d\n", itControlChangeEvent->Param.CC.Controller, itControlChangeEvent->Param.CC.Value)); |
dmsg(4,("Engine::ContinuousController cc=%d v=%d\n", itControlChangeEvent->Param.CC.Controller, itControlChangeEvent->Param.CC.Value)); |
1620 |
|
|
1621 |
|
// handle the "control triggered" MIDI rule: a control change |
1622 |
|
// event can trigger a new note on or note off event |
1623 |
|
if (pEngineChannel->pInstrument) { |
1624 |
|
|
1625 |
|
::gig::MidiRule* rule; |
1626 |
|
for (int i = 0 ; (rule = pEngineChannel->pInstrument->GetMidiRule(i)) ; i++) { |
1627 |
|
|
1628 |
|
if (::gig::MidiRuleCtrlTrigger* ctrlTrigger = |
1629 |
|
dynamic_cast< ::gig::MidiRuleCtrlTrigger*>(rule)) { |
1630 |
|
if (itControlChangeEvent->Param.CC.Controller == |
1631 |
|
ctrlTrigger->ControllerNumber) { |
1632 |
|
|
1633 |
|
uint8_t oldCCValue = pEngineChannel->ControllerTable[ |
1634 |
|
itControlChangeEvent->Param.CC.Controller]; |
1635 |
|
uint8_t newCCValue = itControlChangeEvent->Param.CC.Value; |
1636 |
|
|
1637 |
|
for (int i = 0 ; i < ctrlTrigger->Triggers ; i++) { |
1638 |
|
::gig::MidiRuleCtrlTrigger::trigger_t* pTrigger = |
1639 |
|
&ctrlTrigger->pTriggers[i]; |
1640 |
|
|
1641 |
|
// check if the controller has passed the |
1642 |
|
// trigger point in the right direction |
1643 |
|
if ((pTrigger->Descending && |
1644 |
|
oldCCValue > pTrigger->TriggerPoint && |
1645 |
|
newCCValue <= pTrigger->TriggerPoint) || |
1646 |
|
(!pTrigger->Descending && |
1647 |
|
oldCCValue < pTrigger->TriggerPoint && |
1648 |
|
newCCValue >= pTrigger->TriggerPoint)) { |
1649 |
|
|
1650 |
|
RTList<Event>::Iterator itNewEvent = pGlobalEvents->allocAppend(); |
1651 |
|
if (itNewEvent) { |
1652 |
|
*itNewEvent = *itControlChangeEvent; |
1653 |
|
itNewEvent->Param.Note.Key = pTrigger->Key; |
1654 |
|
|
1655 |
|
if (pTrigger->NoteOff || pTrigger->Velocity == 0) { |
1656 |
|
itNewEvent->Type = Event::type_note_off; |
1657 |
|
itNewEvent->Param.Note.Velocity = 100; |
1658 |
|
|
1659 |
|
ProcessNoteOff(pEngineChannel, itNewEvent); |
1660 |
|
} else { |
1661 |
|
itNewEvent->Type = Event::type_note_on; |
1662 |
|
//TODO: if Velocity is 255, the triggered velocity should |
1663 |
|
// depend on how fast the controller is moving |
1664 |
|
itNewEvent->Param.Note.Velocity = |
1665 |
|
pTrigger->Velocity == 255 ? 100 : |
1666 |
|
pTrigger->Velocity; |
1667 |
|
|
1668 |
|
ProcessNoteOn(pEngineChannel, itNewEvent); |
1669 |
|
} |
1670 |
|
} |
1671 |
|
else dmsg(1,("Event pool emtpy!\n")); |
1672 |
|
} |
1673 |
|
} |
1674 |
|
} |
1675 |
|
} |
1676 |
|
} |
1677 |
|
} |
1678 |
|
|
1679 |
// update controller value in the engine channel's controller table |
// update controller value in the engine channel's controller table |
1680 |
pEngineChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; |
pEngineChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; |
1681 |
|
|
1849 |
if (!pEngineChannel->fxSends.empty()) { |
if (!pEngineChannel->fxSends.empty()) { |
1850 |
for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { |
for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { |
1851 |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
1852 |
if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) |
if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) { |
1853 |
pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); |
pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); |
1854 |
pFxSend->SetInfoChanged(true); |
pFxSend->SetInfoChanged(true); |
1855 |
|
} |
1856 |
} |
} |
1857 |
} |
} |
1858 |
} |
} |
2045 |
} |
} |
2046 |
|
|
2047 |
String Engine::Description() { |
String Engine::Description() { |
2048 |
return "Gigasampler Engine"; |
return "Gigasampler Format Engine"; |
2049 |
} |
} |
2050 |
|
|
2051 |
String Engine::Version() { |
String Engine::Version() { |
2052 |
String s = "$Revision: 1.81 $"; |
String s = "$Revision: 1.89 $"; |
2053 |
return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword |
return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword |
2054 |
} |
} |
2055 |
|
|
2058 |
} |
} |
2059 |
|
|
2060 |
// static constant initializers |
// static constant initializers |
2061 |
const float* Engine::VolumeCurve(InitVolumeCurve()); |
const Engine::FloatTable Engine::VolumeCurve(InitVolumeCurve()); |
2062 |
const float* Engine::PanCurve(InitPanCurve()); |
const Engine::FloatTable Engine::PanCurve(InitPanCurve()); |
2063 |
const float* Engine::CrossfadeCurve(InitCrossfadeCurve()); |
const Engine::FloatTable Engine::CrossfadeCurve(InitCrossfadeCurve()); |
2064 |
|
|
2065 |
float* Engine::InitVolumeCurve() { |
float* Engine::InitVolumeCurve() { |
2066 |
// line-segment approximation |
// line-segment approximation |
2099 |
return y; |
return y; |
2100 |
} |
} |
2101 |
|
|
|
/** |
|
|
* Changes the instrument for an engine channel. |
|
|
* |
|
|
* @param pEngineChannel - engine channel on which the instrument |
|
|
* should be changed |
|
|
* @param pInstrument - new instrument |
|
|
* @returns a list of dimension regions from the old instrument |
|
|
* that are still in use |
|
|
*/ |
|
|
::gig::DimensionRegion** Engine::ChangeInstrument(EngineChannel* pEngineChannel, ::gig::Instrument* pInstrument) { |
|
|
instrument_change_command_t command; |
|
|
command.pEngineChannel = pEngineChannel; |
|
|
command.pInstrument = pInstrument; |
|
|
InstrumentChangeQueue->push(&command); |
|
|
|
|
|
// wait for the audio thread to confirm that the instrument |
|
|
// change has been done |
|
|
instrument_change_reply_t reply; |
|
|
while (InstrumentChangeReplyQueue->pop(&reply) == 0) { |
|
|
usleep(10000); |
|
|
} |
|
|
return pDimRegionsInUse; |
|
|
} |
|
|
|
|
2102 |
}} // namespace LinuxSampler::gig |
}} // namespace LinuxSampler::gig |