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; |
100 |
/** |
/** |
101 |
* Constructor |
* Constructor |
102 |
*/ |
*/ |
103 |
Engine::Engine() { |
Engine::Engine() : SuspendedRegions(128) { |
104 |
pAudioOutputDevice = NULL; |
pAudioOutputDevice = NULL; |
105 |
pDiskThread = NULL; |
pDiskThread = NULL; |
106 |
pEventGenerator = NULL; |
pEventGenerator = NULL; |
121 |
|
|
122 |
ResetInternal(); |
ResetInternal(); |
123 |
ResetScaleTuning(); |
ResetScaleTuning(); |
124 |
|
ResetSuspendedRegions(); |
125 |
} |
} |
126 |
|
|
127 |
/** |
/** |
144 |
if (pEventGenerator) delete pEventGenerator; |
if (pEventGenerator) delete pEventGenerator; |
145 |
if (pVoiceStealingQueue) delete pVoiceStealingQueue; |
if (pVoiceStealingQueue) delete pVoiceStealingQueue; |
146 |
if (pSysexBuffer) delete pSysexBuffer; |
if (pSysexBuffer) delete pSysexBuffer; |
147 |
|
if (pGlobalEvents) delete pGlobalEvents; |
148 |
|
if (InstrumentChangeQueue) delete InstrumentChangeQueue; |
149 |
|
if (InstrumentChangeReplyQueue) delete InstrumentChangeReplyQueue; |
150 |
|
if (pDimRegionsInUse) delete[] pDimRegionsInUse; |
151 |
|
ResetSuspendedRegions(); |
152 |
Unregister(); |
Unregister(); |
153 |
} |
} |
154 |
|
|
158 |
dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe())); |
dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe())); |
159 |
} |
} |
160 |
|
|
161 |
|
/** |
162 |
|
* Temporarily stop the engine to not do anything. The engine will just be |
163 |
|
* frozen during that time, that means after enabling it again it will |
164 |
|
* continue where it was, with all its voices and playback state it had at |
165 |
|
* the point of disabling. Notice that the engine's (audio) thread will |
166 |
|
* continue to run, it just remains in an inactive loop during that time. |
167 |
|
* |
168 |
|
* If you need to be sure that all voices and disk streams are killed as |
169 |
|
* well, use @c SuspendAll() instead. |
170 |
|
* |
171 |
|
* @see Enable(), SuspendAll() |
172 |
|
*/ |
173 |
void Engine::Disable() { |
void Engine::Disable() { |
174 |
dmsg(3,("gig::Engine: disabling\n")); |
dmsg(3,("gig::Engine: disabling\n")); |
175 |
bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s |
bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s |
183 |
} |
} |
184 |
|
|
185 |
/** |
/** |
186 |
|
* Similar to @c Disable() but this method additionally kills all voices |
187 |
|
* and disk streams and blocks until all voices and disk streams are actually |
188 |
|
* killed / deleted. |
189 |
|
* |
190 |
|
* @e Note: only the original calling thread is able to re-enable the |
191 |
|
* engine afterwards by calling @c ResumeAll() later on! |
192 |
|
*/ |
193 |
|
void Engine::SuspendAll() { |
194 |
|
dmsg(2,("gig::Engine: Suspending all ...\n")); |
195 |
|
// stop the engine, so we can safely modify the engine's |
196 |
|
// data structures from this foreign thread |
197 |
|
DisableAndLock(); |
198 |
|
// we could also use the respective class member variable here, |
199 |
|
// but this is probably safer and cleaner |
200 |
|
int iPendingStreamDeletions = 0; |
201 |
|
// kill all voices on all engine channels the *die hard* way |
202 |
|
for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { |
203 |
|
EngineChannel* pEngineChannel = engineChannels[iChannel]; |
204 |
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
205 |
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
206 |
|
for (; iuiKey != end; ++iuiKey) { // iterate through all active keys |
207 |
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
208 |
|
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
209 |
|
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
210 |
|
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
211 |
|
// request a notification from disk thread side for stream deletion |
212 |
|
const Stream::Handle hStream = itVoice->KillImmediately(true); |
213 |
|
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
214 |
|
iPendingStreamDeletions++; |
215 |
|
} |
216 |
|
} |
217 |
|
} |
218 |
|
} |
219 |
|
// wait until all streams were actually deleted by the disk thread |
220 |
|
while (iPendingStreamDeletions) { |
221 |
|
while ( |
222 |
|
iPendingStreamDeletions && |
223 |
|
pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE |
224 |
|
) iPendingStreamDeletions--; |
225 |
|
if (!iPendingStreamDeletions) break; |
226 |
|
usleep(10000); // sleep for 10ms |
227 |
|
} |
228 |
|
dmsg(2,("gig::Engine: Everything suspended.\n")); |
229 |
|
} |
230 |
|
|
231 |
|
/** |
232 |
|
* At the moment same as calling @c Enable() directly, but this might |
233 |
|
* change in future, so better call this method as counterpart to |
234 |
|
* @c SuspendAll() instead of @c Enable() ! |
235 |
|
*/ |
236 |
|
void Engine::ResumeAll() { |
237 |
|
Enable(); |
238 |
|
} |
239 |
|
|
240 |
|
/** |
241 |
|
* Order the engine to stop rendering audio for the given region. |
242 |
|
* Additionally this method will block until all voices and their disk |
243 |
|
* streams associated with that region are actually killed / deleted, so |
244 |
|
* one can i.e. safely modify the region with an instrument editor after |
245 |
|
* returning from this method. |
246 |
|
* |
247 |
|
* @param pRegion - region the engine shall stop using |
248 |
|
*/ |
249 |
|
void Engine::Suspend(::gig::Region* pRegion) { |
250 |
|
dmsg(2,("gig::Engine: Suspending Region %x ...\n",pRegion)); |
251 |
|
SuspendedRegionsMutex.Lock(); |
252 |
|
SuspensionChangeOngoing.Set(true); |
253 |
|
pPendingRegionSuspension = pRegion; |
254 |
|
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
255 |
|
SuspendedRegionsMutex.Unlock(); |
256 |
|
dmsg(2,("gig::Engine: Region %x suspended.",pRegion)); |
257 |
|
} |
258 |
|
|
259 |
|
/** |
260 |
|
* Orders the engine to resume playing back the given region, previously |
261 |
|
* suspended with @c Suspend() . |
262 |
|
* |
263 |
|
* @param pRegion - region the engine shall be allowed to use again |
264 |
|
*/ |
265 |
|
void Engine::Resume(::gig::Region* pRegion) { |
266 |
|
dmsg(2,("gig::Engine: Resuming Region %x ...\n",pRegion)); |
267 |
|
SuspendedRegionsMutex.Lock(); |
268 |
|
SuspensionChangeOngoing.Set(true); |
269 |
|
pPendingRegionResumption = pRegion; |
270 |
|
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
271 |
|
SuspendedRegionsMutex.Unlock(); |
272 |
|
dmsg(2,("gig::Engine: Region %x resumed.\n",pRegion)); |
273 |
|
} |
274 |
|
|
275 |
|
/** |
276 |
* Reset all voices and disk thread and clear input event queue and all |
* Reset all voices and disk thread and clear input event queue and all |
277 |
* control and status variables. |
* control and status variables. |
278 |
*/ |
*/ |
327 |
memset(&ScaleTuning[0], 0x00, 12); |
memset(&ScaleTuning[0], 0x00, 12); |
328 |
} |
} |
329 |
|
|
330 |
|
void Engine::ResetSuspendedRegions() { |
331 |
|
SuspendedRegions.clear(); |
332 |
|
iPendingStreamDeletions = 0; |
333 |
|
pPendingRegionSuspension = pPendingRegionResumption = NULL; |
334 |
|
SuspensionChangeOngoing.Set(false); |
335 |
|
} |
336 |
|
|
337 |
/** |
/** |
338 |
* Connect this engine instance with the given audio output device. |
* Connect this engine instance with the given audio output device. |
339 |
* This method will be called when an Engine instance is created. |
* This method will be called when an Engine instance is created. |
413 |
} |
} |
414 |
|
|
415 |
/** |
/** |
416 |
|
* Called by the engine's (audio) thread once per cycle to process requests |
417 |
|
* from the outer world to suspend or resume a given @c gig::Region . |
418 |
|
*/ |
419 |
|
void Engine::ProcessSuspensionsChanges() { |
420 |
|
// process request for suspending one region |
421 |
|
if (pPendingRegionSuspension) { |
422 |
|
// kill all voices on all engine channels that use this region |
423 |
|
for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { |
424 |
|
EngineChannel* pEngineChannel = engineChannels[iChannel]; |
425 |
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
426 |
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
427 |
|
for (; iuiKey != end; ++iuiKey) { // iterate through all active keys |
428 |
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
429 |
|
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
430 |
|
// if current key is not associated with this region, skip this key |
431 |
|
if (itVoice->pDimRgn->GetParent() != pPendingRegionSuspension) continue; |
432 |
|
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
433 |
|
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
434 |
|
// request a notification from disk thread side for stream deletion |
435 |
|
const Stream::Handle hStream = itVoice->KillImmediately(true); |
436 |
|
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
437 |
|
iPendingStreamDeletions++; |
438 |
|
} |
439 |
|
} |
440 |
|
} |
441 |
|
} |
442 |
|
// make sure the region is not yet on the list |
443 |
|
bool bAlreadySuspended = false; |
444 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); |
445 |
|
RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); |
446 |
|
for (; iter != end; ++iter) { // iterate through all suspended regions |
447 |
|
if (*iter == pPendingRegionSuspension) { // found |
448 |
|
bAlreadySuspended = true; |
449 |
|
dmsg(1,("gig::Engine: attempt to suspend an already suspended region !!!\n")); |
450 |
|
break; |
451 |
|
} |
452 |
|
} |
453 |
|
if (!bAlreadySuspended) { |
454 |
|
// put the region on the list of suspended regions |
455 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.allocAppend(); |
456 |
|
if (iter) { |
457 |
|
*iter = pPendingRegionSuspension; |
458 |
|
} else std::cerr << "gig::Engine: Could not suspend Region, list is full. This is a bug!!!\n" << std::flush; |
459 |
|
} |
460 |
|
// free request slot for next caller (and to make sure that |
461 |
|
// we're not going to process the same request in the next cycle) |
462 |
|
pPendingRegionSuspension = NULL; |
463 |
|
// if no disk stream deletions are pending, awaker other side, as |
464 |
|
// we're done in this case |
465 |
|
if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); |
466 |
|
} |
467 |
|
|
468 |
|
// process request for resuming one region |
469 |
|
if (pPendingRegionResumption) { |
470 |
|
// remove region from the list of suspended regions |
471 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); |
472 |
|
RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); |
473 |
|
for (; iter != end; ++iter) { // iterate through all suspended regions |
474 |
|
if (*iter == pPendingRegionResumption) { // found |
475 |
|
SuspendedRegions.free(iter); |
476 |
|
break; // done |
477 |
|
} |
478 |
|
} |
479 |
|
// free request slot for next caller |
480 |
|
pPendingRegionResumption = NULL; |
481 |
|
// awake other side as we're done |
482 |
|
SuspensionChangeOngoing.Set(false); |
483 |
|
} |
484 |
|
} |
485 |
|
|
486 |
|
/** |
487 |
|
* Called by the engine's (audio) thread once per cycle to check if |
488 |
|
* streams of voices that were killed due to suspension request have |
489 |
|
* finally really been deleted by the disk thread. |
490 |
|
*/ |
491 |
|
void Engine::ProcessPendingStreamDeletions() { |
492 |
|
if (!iPendingStreamDeletions) return; |
493 |
|
//TODO: or shall we better store a list with stream handles instead of a scalar amount of streams to be deleted? might be safer |
494 |
|
while ( |
495 |
|
iPendingStreamDeletions && |
496 |
|
pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE |
497 |
|
) iPendingStreamDeletions--; |
498 |
|
// just for safety ... |
499 |
|
while (pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE); |
500 |
|
// now that all disk streams are deleted, awake other side as |
501 |
|
// we're finally done with suspending the requested region |
502 |
|
if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); |
503 |
|
} |
504 |
|
|
505 |
|
/** |
506 |
|
* Returns @c true if the given region is currently set to be suspended |
507 |
|
* from being used, @c false otherwise. |
508 |
|
*/ |
509 |
|
bool Engine::RegionSuspended(::gig::Region* pRegion) { |
510 |
|
if (SuspendedRegions.isEmpty()) return false; |
511 |
|
//TODO: or shall we use a sorted container instead of the RTList? might be faster ... or trivial ;-) |
512 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); |
513 |
|
RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); |
514 |
|
for (; iter != end; ++iter) // iterate through all suspended regions |
515 |
|
if (*iter == pRegion) return true; |
516 |
|
return false; |
517 |
|
} |
518 |
|
|
519 |
|
/** |
520 |
* Clear all engine global event lists. |
* Clear all engine global event lists. |
521 |
*/ |
*/ |
522 |
void Engine::ClearEventLists() { |
void Engine::ClearEventLists() { |
579 |
return 0; |
return 0; |
580 |
} |
} |
581 |
|
|
582 |
|
// process requests for suspending / resuming regions (i.e. to avoid |
583 |
|
// crashes while these regions are modified by an instrument editor) |
584 |
|
ProcessSuspensionsChanges(); |
585 |
|
|
586 |
// update time of start and end of this audio fragment (as events' time stamps relate to this) |
// update time of start and end of this audio fragment (as events' time stamps relate to this) |
587 |
pEventGenerator->UpdateFragmentTime(Samples); |
pEventGenerator->UpdateFragmentTime(Samples); |
588 |
|
|
618 |
EngineChannel* pEngineChannel = command.pEngineChannel; |
EngineChannel* pEngineChannel = command.pEngineChannel; |
619 |
pEngineChannel->pInstrument = command.pInstrument; |
pEngineChannel->pInstrument = command.pInstrument; |
620 |
|
|
621 |
|
//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 |
622 |
|
ResetSuspendedRegions(); |
623 |
|
|
624 |
// iterate through all active voices and mark their |
// iterate through all active voices and mark their |
625 |
// dimension regions as "in use". The instrument resource |
// dimension regions as "in use". The instrument resource |
626 |
// manager may delete all of the instrument except the |
// manager may delete all of the instrument except the |
683 |
ActiveVoiceCount = ActiveVoiceCountTemp; |
ActiveVoiceCount = ActiveVoiceCountTemp; |
684 |
if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount; |
if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount; |
685 |
|
|
686 |
|
// in case regions were previously suspended and we killed voices |
687 |
|
// with disk streams due to that, check if those streams have finally |
688 |
|
// been deleted by the disk thread |
689 |
|
if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); |
690 |
|
|
691 |
FrameTime += Samples; |
FrameTime += Samples; |
692 |
|
|
693 |
return 0; |
return 0; |
755 |
if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted |
if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted |
756 |
#endif |
#endif |
757 |
|
|
758 |
|
uint voiceCount = 0; |
759 |
|
uint streamCount = 0; |
760 |
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
761 |
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
762 |
while (iuiKey != end) { // iterate through all active keys |
while (iuiKey != end) { // iterate through all active keys |
768 |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
769 |
// now render current voice |
// now render current voice |
770 |
itVoice->Render(Samples); |
itVoice->Render(Samples); |
771 |
if (itVoice->IsActive()) ActiveVoiceCountTemp++; // still active |
if (itVoice->IsActive()) { // still active |
772 |
else { // voice reached end, is now inactive |
ActiveVoiceCountTemp++; |
773 |
|
voiceCount++; |
774 |
|
|
775 |
|
if (itVoice->PlaybackState == Voice::playback_state_disk) { |
776 |
|
if ((itVoice->DiskStreamRef).State == Stream::state_active) streamCount++; |
777 |
|
} |
778 |
|
} else { // voice reached end, is now inactive |
779 |
FreeVoice(pEngineChannel, itVoice); // remove voice from the list of active voices |
FreeVoice(pEngineChannel, itVoice); // remove voice from the list of active voices |
780 |
} |
} |
781 |
} |
} |
782 |
} |
} |
783 |
|
|
784 |
|
pEngineChannel->SetVoiceCount(voiceCount); |
785 |
|
pEngineChannel->SetDiskStreamCount(streamCount); |
786 |
} |
} |
787 |
|
|
788 |
/** |
/** |
808 |
LaunchVoice(pEngineChannel, itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false, false); |
LaunchVoice(pEngineChannel, itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false, false); |
809 |
if (itNewVoice) { |
if (itNewVoice) { |
810 |
itNewVoice->Render(Samples); |
itNewVoice->Render(Samples); |
811 |
if (itNewVoice->IsActive()) ActiveVoiceCountTemp++; // still active |
if (itNewVoice->IsActive()) { // still active |
812 |
else { // voice reached end, is now inactive |
ActiveVoiceCountTemp++; |
813 |
|
pEngineChannel->SetVoiceCount(pEngineChannel->GetVoiceCount() + 1); |
814 |
|
|
815 |
|
if (itNewVoice->PlaybackState == Voice::playback_state_disk) { |
816 |
|
if (itNewVoice->DiskStreamRef.State == Stream::state_active) { |
817 |
|
pEngineChannel->SetDiskStreamCount(pEngineChannel->GetDiskStreamCount() + 1); |
818 |
|
} |
819 |
|
} |
820 |
|
} else { // voice reached end, is now inactive |
821 |
FreeVoice(pEngineChannel, itNewVoice); // remove voice from the list of active voices |
FreeVoice(pEngineChannel, itNewVoice); // remove voice from the list of active voices |
822 |
} |
} |
823 |
} |
} |
961 |
|
|
962 |
if (!pEngineChannel->pInstrument) return; // ignore if no instrument loaded |
if (!pEngineChannel->pInstrument) return; // ignore if no instrument loaded |
963 |
|
|
964 |
|
//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 |
965 |
|
itNoteOnEvent->Param.Note.Key += pEngineChannel->GlobalTranspose; |
966 |
|
|
967 |
const int key = itNoteOnEvent->Param.Note.Key; |
const int key = itNoteOnEvent->Param.Note.Key; |
968 |
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[key]; |
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[key]; |
969 |
|
|
1021 |
{ |
{ |
1022 |
// first, get total amount of required voices (dependant on amount of layers) |
// first, get total amount of required voices (dependant on amount of layers) |
1023 |
::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOnEventOnKeyList->Param.Note.Key); |
::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOnEventOnKeyList->Param.Note.Key); |
1024 |
if (pRegion) { |
if (pRegion && !RegionSuspended(pRegion)) { |
1025 |
int voicesRequired = pRegion->Layers; |
int voicesRequired = pRegion->Layers; |
1026 |
// now launch the required amount of voices |
// now launch the required amount of voices |
1027 |
for (int i = 0; i < voicesRequired; i++) |
for (int i = 0; i < voicesRequired; i++) |
1051 |
if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted |
if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted |
1052 |
#endif |
#endif |
1053 |
|
|
1054 |
|
//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 |
1055 |
|
itNoteOffEvent->Param.Note.Key += pEngineChannel->GlobalTranspose; |
1056 |
|
|
1057 |
const int iKey = itNoteOffEvent->Param.Note.Key; |
const int iKey = itNoteOffEvent->Param.Note.Key; |
1058 |
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[iKey]; |
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[iKey]; |
1059 |
pKey->KeyPressed = false; // the MIDI key was now released |
pKey->KeyPressed = false; // the MIDI key was now released |
1614 |
pEngineChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN; |
pEngineChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN; |
1615 |
break; |
break; |
1616 |
} |
} |
1617 |
|
case 6: { // data entry (currently only used for RPN controllers) |
1618 |
|
if (pEngineChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones |
1619 |
|
int transpose = (int) itControlChangeEvent->Param.CC.Value - 64; |
1620 |
|
// limit to +- two octaves for now |
1621 |
|
transpose = RTMath::Min(transpose, 24); |
1622 |
|
transpose = RTMath::Max(transpose, -24); |
1623 |
|
pEngineChannel->GlobalTranspose = transpose; |
1624 |
|
// workaround, so we won't have hanging notes |
1625 |
|
ReleaseAllVoices(pEngineChannel, itControlChangeEvent); |
1626 |
|
} |
1627 |
|
// to avoid other MIDI CC #6 messages to be misenterpreted as RPN controller data |
1628 |
|
pEngineChannel->ResetMidiRpnController(); |
1629 |
|
break; |
1630 |
|
} |
1631 |
case 7: { // volume |
case 7: { // volume |
1632 |
//TODO: not sample accurate yet |
//TODO: not sample accurate yet |
1633 |
pEngineChannel->MidiVolume = VolumeCurve[itControlChangeEvent->Param.CC.Value]; |
pEngineChannel->MidiVolume = VolumeCurve[itControlChangeEvent->Param.CC.Value]; |
1688 |
break; |
break; |
1689 |
} |
} |
1690 |
case 65: { // portamento on / off |
case 65: { // portamento on / off |
1691 |
KillAllVoices(pEngineChannel, itControlChangeEvent); |
const bool bPortamento = itControlChangeEvent->Param.CC.Value >= 64; |
1692 |
pEngineChannel->PortamentoMode = itControlChangeEvent->Param.CC.Value >= 64; |
if (bPortamento != pEngineChannel->PortamentoMode) |
1693 |
|
KillAllVoices(pEngineChannel, itControlChangeEvent); |
1694 |
|
pEngineChannel->PortamentoMode = bPortamento; |
1695 |
break; |
break; |
1696 |
} |
} |
1697 |
case 66: { // sostenuto |
case 66: { // sostenuto |
1734 |
} |
} |
1735 |
break; |
break; |
1736 |
} |
} |
1737 |
|
case 100: { // RPN controller LSB |
1738 |
|
pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); |
1739 |
|
break; |
1740 |
|
} |
1741 |
|
case 101: { // RPN controller MSB |
1742 |
|
pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value); |
1743 |
|
break; |
1744 |
|
} |
1745 |
|
|
1746 |
|
|
1747 |
// Channel Mode Messages |
// Channel Mode Messages |
1761 |
break; |
break; |
1762 |
} |
} |
1763 |
case 126: { // mono mode on |
case 126: { // mono mode on |
1764 |
KillAllVoices(pEngineChannel, itControlChangeEvent); |
if (!pEngineChannel->SoloMode) |
1765 |
|
KillAllVoices(pEngineChannel, itControlChangeEvent); |
1766 |
pEngineChannel->SoloMode = true; |
pEngineChannel->SoloMode = true; |
1767 |
break; |
break; |
1768 |
} |
} |
1769 |
case 127: { // poly mode on |
case 127: { // poly mode on |
1770 |
KillAllVoices(pEngineChannel, itControlChangeEvent); |
if (pEngineChannel->SoloMode) |
1771 |
|
KillAllVoices(pEngineChannel, itControlChangeEvent); |
1772 |
pEngineChannel->SoloMode = false; |
pEngineChannel->SoloMode = false; |
1773 |
break; |
break; |
1774 |
} |
} |
1780 |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
1781 |
if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) |
if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) |
1782 |
pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); |
pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); |
1783 |
|
pFxSend->SetInfoChanged(true); |
1784 |
} |
} |
1785 |
} |
} |
1786 |
} |
} |
1977 |
} |
} |
1978 |
|
|
1979 |
String Engine::Version() { |
String Engine::Version() { |
1980 |
String s = "$Revision: 1.72 $"; |
String s = "$Revision: 1.83 $"; |
1981 |
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 |
1982 |
} |
} |
1983 |
|
|