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-2009 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 |
* |
* |
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; |
107 |
pSysexBuffer = new RingBuffer<uint8_t,false>(CONFIG_SYSEX_BUFFER_SIZE, 0); |
pSysexBuffer = new RingBuffer<uint8_t,false>(CONFIG_SYSEX_BUFFER_SIZE, 0); |
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>(GLOBAL_MAX_VOICES); |
111 |
pDimRegionsInUse = new ::gig::DimensionRegion*[CONFIG_MAX_VOICES + 1]; |
pDimRegionPool[0] = new Pool< ::gig::DimensionRegion*>(GLOBAL_MAX_VOICES); |
112 |
|
pDimRegionPool[1] = new Pool< ::gig::DimensionRegion*>(GLOBAL_MAX_VOICES); |
113 |
pVoiceStealingQueue = new RTList<Event>(pEventPool); |
pVoiceStealingQueue = new RTList<Event>(pEventPool); |
114 |
pGlobalEvents = new RTList<Event>(pEventPool); |
pGlobalEvents = new RTList<Event>(pEventPool); |
115 |
InstrumentChangeQueue = new RingBuffer<instrument_change_command_t,false>(1, 0); |
iMaxDiskStreams = GLOBAL_MAX_STREAMS; |
|
InstrumentChangeReplyQueue = new RingBuffer<instrument_change_reply_t,false>(1, 0); |
|
116 |
|
|
117 |
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()) { |
118 |
iterVoice->SetEngine(this); |
iterVoice->SetEngine(this); |
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 (pDimRegionPool[0]) delete pDimRegionPool[0]; |
149 |
|
if (pDimRegionPool[1]) delete pDimRegionPool[1]; |
150 |
|
ResetSuspendedRegions(); |
151 |
Unregister(); |
Unregister(); |
152 |
} |
} |
153 |
|
|
157 |
dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe())); |
dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe())); |
158 |
} |
} |
159 |
|
|
160 |
|
/** |
161 |
|
* Temporarily stop the engine to not do anything. The engine will just be |
162 |
|
* frozen during that time, that means after enabling it again it will |
163 |
|
* continue where it was, with all its voices and playback state it had at |
164 |
|
* the point of disabling. Notice that the engine's (audio) thread will |
165 |
|
* continue to run, it just remains in an inactive loop during that time. |
166 |
|
* |
167 |
|
* If you need to be sure that all voices and disk streams are killed as |
168 |
|
* well, use @c SuspendAll() instead. |
169 |
|
* |
170 |
|
* @see Enable(), SuspendAll() |
171 |
|
*/ |
172 |
void Engine::Disable() { |
void Engine::Disable() { |
173 |
dmsg(3,("gig::Engine: disabling\n")); |
dmsg(3,("gig::Engine: disabling\n")); |
174 |
bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s |
bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s |
182 |
} |
} |
183 |
|
|
184 |
/** |
/** |
185 |
|
* Similar to @c Disable() but this method additionally kills all voices |
186 |
|
* and disk streams and blocks until all voices and disk streams are actually |
187 |
|
* killed / deleted. |
188 |
|
* |
189 |
|
* @e Note: only the original calling thread is able to re-enable the |
190 |
|
* engine afterwards by calling @c ResumeAll() later on! |
191 |
|
*/ |
192 |
|
void Engine::SuspendAll() { |
193 |
|
dmsg(2,("gig::Engine: Suspending all ...\n")); |
194 |
|
// stop the engine, so we can safely modify the engine's |
195 |
|
// data structures from this foreign thread |
196 |
|
DisableAndLock(); |
197 |
|
// we could also use the respective class member variable here, |
198 |
|
// but this is probably safer and cleaner |
199 |
|
int iPendingStreamDeletions = 0; |
200 |
|
// kill all voices on all engine channels the *die hard* way |
201 |
|
for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { |
202 |
|
EngineChannel* pEngineChannel = engineChannels[iChannel]; |
203 |
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
204 |
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
205 |
|
for (; iuiKey != end; ++iuiKey) { // iterate through all active keys |
206 |
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
207 |
|
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
208 |
|
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
209 |
|
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
210 |
|
// request a notification from disk thread side for stream deletion |
211 |
|
const Stream::Handle hStream = itVoice->KillImmediately(true); |
212 |
|
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
213 |
|
iPendingStreamDeletions++; |
214 |
|
} |
215 |
|
// free the voice to the voice pool and update key info |
216 |
|
FreeVoice(pEngineChannel, itVoice); |
217 |
|
} |
218 |
|
} |
219 |
|
} |
220 |
|
// wait until all streams were actually deleted by the disk thread |
221 |
|
while (iPendingStreamDeletions) { |
222 |
|
while ( |
223 |
|
iPendingStreamDeletions && |
224 |
|
pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE |
225 |
|
) iPendingStreamDeletions--; |
226 |
|
if (!iPendingStreamDeletions) break; |
227 |
|
usleep(10000); // sleep for 10ms |
228 |
|
} |
229 |
|
dmsg(2,("gig::Engine: Everything suspended.\n")); |
230 |
|
} |
231 |
|
|
232 |
|
/** |
233 |
|
* At the moment same as calling @c Enable() directly, but this might |
234 |
|
* change in future, so better call this method as counterpart to |
235 |
|
* @c SuspendAll() instead of @c Enable() ! |
236 |
|
*/ |
237 |
|
void Engine::ResumeAll() { |
238 |
|
Enable(); |
239 |
|
} |
240 |
|
|
241 |
|
/** |
242 |
|
* Order the engine to stop rendering audio for the given region. |
243 |
|
* Additionally this method will block until all voices and their disk |
244 |
|
* streams associated with that region are actually killed / deleted, so |
245 |
|
* one can i.e. safely modify the region with an instrument editor after |
246 |
|
* returning from this method. |
247 |
|
* |
248 |
|
* @param pRegion - region the engine shall stop using |
249 |
|
*/ |
250 |
|
void Engine::Suspend(::gig::Region* pRegion) { |
251 |
|
dmsg(2,("gig::Engine: Suspending Region %x ...\n",pRegion)); |
252 |
|
SuspendedRegionsMutex.Lock(); |
253 |
|
SuspensionChangeOngoing.Set(true); |
254 |
|
pPendingRegionSuspension = pRegion; |
255 |
|
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
256 |
|
SuspendedRegionsMutex.Unlock(); |
257 |
|
dmsg(2,("gig::Engine: Region %x suspended.",pRegion)); |
258 |
|
} |
259 |
|
|
260 |
|
/** |
261 |
|
* Orders the engine to resume playing back the given region, previously |
262 |
|
* suspended with @c Suspend() . |
263 |
|
* |
264 |
|
* @param pRegion - region the engine shall be allowed to use again |
265 |
|
*/ |
266 |
|
void Engine::Resume(::gig::Region* pRegion) { |
267 |
|
dmsg(2,("gig::Engine: Resuming Region %x ...\n",pRegion)); |
268 |
|
SuspendedRegionsMutex.Lock(); |
269 |
|
SuspensionChangeOngoing.Set(true); |
270 |
|
pPendingRegionResumption = pRegion; |
271 |
|
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
272 |
|
SuspendedRegionsMutex.Unlock(); |
273 |
|
dmsg(2,("gig::Engine: Region %x resumed.\n",pRegion)); |
274 |
|
} |
275 |
|
|
276 |
|
/** |
277 |
* 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 |
278 |
* control and status variables. |
* control and status variables. |
279 |
*/ |
*/ |
294 |
// make sure that the engine does not get any sysex messages |
// make sure that the engine does not get any sysex messages |
295 |
// while it's reseting |
// while it's reseting |
296 |
bool sysexDisabled = MidiInputPort::RemoveSysexListener(this); |
bool sysexDisabled = MidiInputPort::RemoveSysexListener(this); |
297 |
ActiveVoiceCount = 0; |
SetVoiceCount(0); |
298 |
ActiveVoiceCountMax = 0; |
ActiveVoiceCountMax = 0; |
299 |
|
|
300 |
// reset voice stealing parameters |
// reset voice stealing parameters |
328 |
memset(&ScaleTuning[0], 0x00, 12); |
memset(&ScaleTuning[0], 0x00, 12); |
329 |
} |
} |
330 |
|
|
331 |
|
void Engine::ResetSuspendedRegions() { |
332 |
|
SuspendedRegions.clear(); |
333 |
|
iPendingStreamDeletions = 0; |
334 |
|
pPendingRegionSuspension = pPendingRegionResumption = NULL; |
335 |
|
SuspensionChangeOngoing.Set(false); |
336 |
|
} |
337 |
|
|
338 |
/** |
/** |
339 |
* Connect this engine instance with the given audio output device. |
* Connect this engine instance with the given audio output device. |
340 |
* This method will be called when an Engine instance is created. |
* This method will be called when an Engine instance is created. |
345 |
* @param pAudioOut - audio output device to connect to |
* @param pAudioOut - audio output device to connect to |
346 |
*/ |
*/ |
347 |
void Engine::Connect(AudioOutputDevice* pAudioOut) { |
void Engine::Connect(AudioOutputDevice* pAudioOut) { |
348 |
|
// caution: don't ignore if connecting to the same device here, |
349 |
|
// because otherwise SetMaxDiskStreams() implementation won't work anymore! |
350 |
|
|
351 |
pAudioOutputDevice = pAudioOut; |
pAudioOutputDevice = pAudioOut; |
352 |
|
|
353 |
ResetInternal(); |
ResetInternal(); |
364 |
this->MaxSamplesPerCycle = pAudioOutputDevice->MaxSamplesPerCycle(); |
this->MaxSamplesPerCycle = pAudioOutputDevice->MaxSamplesPerCycle(); |
365 |
this->SampleRate = pAudioOutputDevice->SampleRate(); |
this->SampleRate = pAudioOutputDevice->SampleRate(); |
366 |
|
|
367 |
// FIXME: audio drivers with varying fragment sizes might be a problem here |
MinFadeOutSamples = int(double(SampleRate) * CONFIG_EG_MIN_RELEASE_TIME) - 1; |
368 |
MaxFadeOutPos = MaxSamplesPerCycle - int(double(SampleRate) * CONFIG_EG_MIN_RELEASE_TIME) - 1; |
if (MaxSamplesPerCycle < MinFadeOutSamples) { |
|
if (MaxFadeOutPos < 0) { |
|
369 |
std::cerr << "gig::Engine: WARNING, CONFIG_EG_MIN_RELEASE_TIME " |
std::cerr << "gig::Engine: WARNING, CONFIG_EG_MIN_RELEASE_TIME " |
370 |
<< "too big for current audio fragment size & sampling rate! " |
<< "too big for current audio fragment size & sampling rate! " |
371 |
<< "May lead to click sounds if voice stealing chimes in!\n" << std::flush; |
<< "May lead to click sounds if voice stealing chimes in!\n" << std::flush; |
372 |
// force volume ramp downs at the beginning of each fragment |
// force volume ramp downs at the beginning of each fragment |
373 |
MaxFadeOutPos = 0; |
MinFadeOutSamples = MaxSamplesPerCycle; |
374 |
// lower minimum release time |
// lower minimum release time |
375 |
const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; |
const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; |
376 |
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()) { |
386 |
delete this->pDiskThread; |
delete this->pDiskThread; |
387 |
dmsg(1,("OK\n")); |
dmsg(1,("OK\n")); |
388 |
} |
} |
389 |
this->pDiskThread = new DiskThread(((pAudioOut->MaxSamplesPerCycle() << CONFIG_MAX_PITCH) << 1) + 6, //FIXME: assuming stereo |
this->pDiskThread = |
390 |
&instruments); |
new DiskThread( |
391 |
|
iMaxDiskStreams, |
392 |
|
((pAudioOut->MaxSamplesPerCycle() << CONFIG_MAX_PITCH) << 1) + 6, //FIXME: assuming stereo |
393 |
|
&instruments |
394 |
|
); |
395 |
|
|
396 |
if (!pDiskThread) { |
if (!pDiskThread) { |
397 |
dmsg(0,("gig::Engine new diskthread = NULL\n")); |
dmsg(0,("gig::Engine new diskthread = NULL\n")); |
398 |
exit(EXIT_FAILURE); |
exit(EXIT_FAILURE); |
418 |
exit(EXIT_FAILURE); |
exit(EXIT_FAILURE); |
419 |
} |
} |
420 |
} |
} |
421 |
|
pVoicePool->clear(); |
422 |
|
} |
423 |
|
|
424 |
|
/** |
425 |
|
* Called by the engine's (audio) thread once per cycle to process requests |
426 |
|
* from the outer world to suspend or resume a given @c gig::Region . |
427 |
|
*/ |
428 |
|
void Engine::ProcessSuspensionsChanges() { |
429 |
|
// process request for suspending one region |
430 |
|
if (pPendingRegionSuspension) { |
431 |
|
// kill all voices on all engine channels that use this region |
432 |
|
for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { |
433 |
|
EngineChannel* pEngineChannel = engineChannels[iChannel]; |
434 |
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
435 |
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
436 |
|
for (; iuiKey != end; ++iuiKey) { // iterate through all active keys |
437 |
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
438 |
|
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
439 |
|
// if current key is not associated with this region, skip this key |
440 |
|
if (itVoice->pDimRgn->GetParent() != pPendingRegionSuspension) continue; |
441 |
|
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
442 |
|
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
443 |
|
// request a notification from disk thread side for stream deletion |
444 |
|
const Stream::Handle hStream = itVoice->KillImmediately(true); |
445 |
|
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
446 |
|
iPendingStreamDeletions++; |
447 |
|
} |
448 |
|
//NOTE: maybe we should call FreeVoice() here, shouldn't cause a harm though I think, since the voices should be freed by RenderActiveVoices() in the render loop, they are probably just freed a bit later than they could/should be |
449 |
|
} |
450 |
|
} |
451 |
|
} |
452 |
|
// make sure the region is not yet on the list |
453 |
|
bool bAlreadySuspended = false; |
454 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); |
455 |
|
RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); |
456 |
|
for (; iter != end; ++iter) { // iterate through all suspended regions |
457 |
|
if (*iter == pPendingRegionSuspension) { // found |
458 |
|
bAlreadySuspended = true; |
459 |
|
dmsg(1,("gig::Engine: attempt to suspend an already suspended region !!!\n")); |
460 |
|
break; |
461 |
|
} |
462 |
|
} |
463 |
|
if (!bAlreadySuspended) { |
464 |
|
// put the region on the list of suspended regions |
465 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.allocAppend(); |
466 |
|
if (iter) { |
467 |
|
*iter = pPendingRegionSuspension; |
468 |
|
} else std::cerr << "gig::Engine: Could not suspend Region, list is full. This is a bug!!!\n" << std::flush; |
469 |
|
} |
470 |
|
// free request slot for next caller (and to make sure that |
471 |
|
// we're not going to process the same request in the next cycle) |
472 |
|
pPendingRegionSuspension = NULL; |
473 |
|
// if no disk stream deletions are pending, awaken other side, as |
474 |
|
// we're done in this case |
475 |
|
if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); |
476 |
|
} |
477 |
|
|
478 |
|
// process request for resuming one region |
479 |
|
if (pPendingRegionResumption) { |
480 |
|
// remove region from the list of suspended regions |
481 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); |
482 |
|
RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); |
483 |
|
for (; iter != end; ++iter) { // iterate through all suspended regions |
484 |
|
if (*iter == pPendingRegionResumption) { // found |
485 |
|
SuspendedRegions.free(iter); |
486 |
|
break; // done |
487 |
|
} |
488 |
|
} |
489 |
|
// free request slot for next caller |
490 |
|
pPendingRegionResumption = NULL; |
491 |
|
// awake other side as we're done |
492 |
|
SuspensionChangeOngoing.Set(false); |
493 |
|
} |
494 |
|
} |
495 |
|
|
496 |
|
/** |
497 |
|
* Called by the engine's (audio) thread once per cycle to check if |
498 |
|
* streams of voices that were killed due to suspension request have |
499 |
|
* finally really been deleted by the disk thread. |
500 |
|
*/ |
501 |
|
void Engine::ProcessPendingStreamDeletions() { |
502 |
|
if (!iPendingStreamDeletions) return; |
503 |
|
//TODO: or shall we better store a list with stream handles instead of a scalar amount of streams to be deleted? might be safer |
504 |
|
while ( |
505 |
|
iPendingStreamDeletions && |
506 |
|
pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE |
507 |
|
) iPendingStreamDeletions--; |
508 |
|
// just for safety ... |
509 |
|
while (pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE); |
510 |
|
// now that all disk streams are deleted, awake other side as |
511 |
|
// we're finally done with suspending the requested region |
512 |
|
if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); |
513 |
|
} |
514 |
|
|
515 |
|
/** |
516 |
|
* Returns @c true if the given region is currently set to be suspended |
517 |
|
* from being used, @c false otherwise. |
518 |
|
*/ |
519 |
|
bool Engine::RegionSuspended(::gig::Region* pRegion) { |
520 |
|
if (SuspendedRegions.isEmpty()) return false; |
521 |
|
//TODO: or shall we use a sorted container instead of the RTList? might be faster ... or trivial ;-) |
522 |
|
RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); |
523 |
|
RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); |
524 |
|
for (; iter != end; ++iter) // iterate through all suspended regions |
525 |
|
if (*iter == pRegion) return true; |
526 |
|
return false; |
527 |
} |
} |
528 |
|
|
529 |
/** |
/** |
581 |
* @returns 0 on success |
* @returns 0 on success |
582 |
*/ |
*/ |
583 |
int Engine::RenderAudio(uint Samples) { |
int Engine::RenderAudio(uint Samples) { |
584 |
dmsg(7,("RenderAudio(Samples=%d)\n", Samples)); |
dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); |
585 |
|
|
586 |
// return if engine disabled |
// return if engine disabled |
587 |
if (EngineDisabled.Pop()) { |
if (EngineDisabled.Pop()) { |
588 |
dmsg(5,("gig::Engine: engine disabled (val=%d)\n",EngineDisabled.GetUnsafe())); |
dmsg(5,("gig::Engine: engine disabled (val=%d)\n",EngineDisabled.GetUnsafe())); |
589 |
|
EngineDisabled.RttDone(); |
590 |
return 0; |
return 0; |
591 |
} |
} |
592 |
|
|
593 |
|
// process requests for suspending / resuming regions (i.e. to avoid |
594 |
|
// crashes while these regions are modified by an instrument editor) |
595 |
|
ProcessSuspensionsChanges(); |
596 |
|
|
597 |
// 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) |
598 |
pEventGenerator->UpdateFragmentTime(Samples); |
pEventGenerator->UpdateFragmentTime(Samples); |
599 |
|
|
600 |
// We only allow a maximum of CONFIG_MAX_VOICES voices to be spawned |
// We only allow the given maximum number of voices to be spawned |
601 |
// in each audio fragment. All subsequent request for spawning new |
// in each audio fragment. All subsequent request for spawning new |
602 |
// voices in the same audio fragment will be ignored. |
// voices in the same audio fragment will be ignored. |
603 |
VoiceSpawnsLeft = CONFIG_MAX_VOICES; |
VoiceSpawnsLeft = MaxVoices(); |
604 |
|
|
605 |
// get all events from the engine's global input event queue which belong to the current fragment |
// get all events from the engine's global input event queue which belong to the current fragment |
606 |
// (these are usually just SysEx messages) |
// (these are usually just SysEx messages) |
624 |
ActiveVoiceCountTemp = 0; |
ActiveVoiceCountTemp = 0; |
625 |
|
|
626 |
// handle instrument change commands |
// handle instrument change commands |
627 |
instrument_change_command_t command; |
bool instrumentChanged = false; |
628 |
if (InstrumentChangeQueue->pop(&command) > 0) { |
for (int i = 0; i < engineChannels.size(); i++) { |
629 |
EngineChannel* pEngineChannel = command.pEngineChannel; |
EngineChannel* pEngineChannel = engineChannels[i]; |
|
pEngineChannel->pInstrument = command.pInstrument; |
|
|
|
|
|
// iterate through all active voices and mark their |
|
|
// dimension regions as "in use". The instrument resource |
|
|
// manager may delete all of the instrument except the |
|
|
// dimension regions and samples that are in use. |
|
|
int i = 0; |
|
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
|
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
|
|
while (iuiKey != end) { // iterate through all active keys |
|
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
|
|
++iuiKey; |
|
630 |
|
|
631 |
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
// as we're going to (carefully) write some status to the |
632 |
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
// synchronized struct, we cast away the const |
633 |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
EngineChannel::instrument_change_command_t& cmd = |
634 |
if (!itVoice->Orphan) { |
const_cast<EngineChannel::instrument_change_command_t&>(pEngineChannel->InstrumentChangeCommandReader.Lock()); |
635 |
|
|
636 |
|
pEngineChannel->pDimRegionsInUse = cmd.pDimRegionsInUse; |
637 |
|
pEngineChannel->pDimRegionsInUse->clear(); |
638 |
|
|
639 |
|
if (cmd.bChangeInstrument) { |
640 |
|
// change instrument |
641 |
|
dmsg(5,("Engine: instrument change command received\n")); |
642 |
|
cmd.bChangeInstrument = false; |
643 |
|
pEngineChannel->pInstrument = cmd.pInstrument; |
644 |
|
instrumentChanged = true; |
645 |
|
|
646 |
|
// Iterate through all active voices and mark them as |
647 |
|
// "orphans", which means that the dimension regions |
648 |
|
// and samples they use should be released to the |
649 |
|
// instrument resource manager when the voices die. |
650 |
|
int i = 0; |
651 |
|
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
652 |
|
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
653 |
|
while (iuiKey != end) { // iterate through all active keys |
654 |
|
midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; |
655 |
|
++iuiKey; |
656 |
|
|
657 |
|
RTList<Voice>::Iterator itVoice = pKey->pActiveVoices->first(); |
658 |
|
RTList<Voice>::Iterator itVoicesEnd = pKey->pActiveVoices->end(); |
659 |
|
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
660 |
itVoice->Orphan = true; |
itVoice->Orphan = true; |
|
pDimRegionsInUse[i++] = itVoice->pDimRgn; |
|
661 |
} |
} |
662 |
} |
} |
663 |
} |
} |
664 |
pDimRegionsInUse[i] = 0; // end of list |
} |
665 |
|
if (instrumentChanged) { |
666 |
// 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 |
667 |
instrument_change_reply_t reply; |
ResetSuspendedRegions(); |
|
InstrumentChangeReplyQueue->push(&reply); |
|
668 |
} |
} |
669 |
|
|
670 |
// handle events on all engine channels |
// handle events on all engine channels |
699 |
pVoiceStealingQueue->clear(); |
pVoiceStealingQueue->clear(); |
700 |
|
|
701 |
// just some statistics about this engine instance |
// just some statistics about this engine instance |
702 |
ActiveVoiceCount = ActiveVoiceCountTemp; |
SetVoiceCount(ActiveVoiceCountTemp); |
703 |
if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount; |
if (VoiceCount() > ActiveVoiceCountMax) ActiveVoiceCountMax = VoiceCount(); |
704 |
|
|
705 |
|
// in case regions were previously suspended and we killed voices |
706 |
|
// with disk streams due to that, check if those streams have finally |
707 |
|
// been deleted by the disk thread |
708 |
|
if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); |
709 |
|
|
710 |
|
for (int i = 0; i < engineChannels.size(); i++) { |
711 |
|
engineChannels[i]->InstrumentChangeCommandReader.Unlock(); |
712 |
|
} |
713 |
FrameTime += Samples; |
FrameTime += Samples; |
714 |
|
|
715 |
|
EngineDisabled.RttDone(); |
716 |
return 0; |
return 0; |
717 |
} |
} |
718 |
|
|
778 |
if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted |
if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted |
779 |
#endif |
#endif |
780 |
|
|
781 |
|
uint voiceCount = 0; |
782 |
|
uint streamCount = 0; |
783 |
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
RTList<uint>::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); |
784 |
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
RTList<uint>::Iterator end = pEngineChannel->pActiveKeys->end(); |
785 |
while (iuiKey != end) { // iterate through all active keys |
while (iuiKey != end) { // iterate through all active keys |
791 |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key |
792 |
// now render current voice |
// now render current voice |
793 |
itVoice->Render(Samples); |
itVoice->Render(Samples); |
794 |
if (itVoice->IsActive()) ActiveVoiceCountTemp++; // still active |
if (itVoice->IsActive()) { // still active |
795 |
else { // voice reached end, is now inactive |
if (!itVoice->Orphan) { |
796 |
|
*(pEngineChannel->pDimRegionsInUse->allocAppend()) = itVoice->pDimRgn; |
797 |
|
} |
798 |
|
ActiveVoiceCountTemp++; |
799 |
|
voiceCount++; |
800 |
|
|
801 |
|
if (itVoice->PlaybackState == Voice::playback_state_disk) { |
802 |
|
if ((itVoice->DiskStreamRef).State != Stream::state_unused) streamCount++; |
803 |
|
} |
804 |
|
} else { // voice reached end, is now inactive |
805 |
FreeVoice(pEngineChannel, itVoice); // remove voice from the list of active voices |
FreeVoice(pEngineChannel, itVoice); // remove voice from the list of active voices |
806 |
} |
} |
807 |
} |
} |
808 |
} |
} |
809 |
|
|
810 |
|
pEngineChannel->SetVoiceCount(voiceCount); |
811 |
|
pEngineChannel->SetDiskStreamCount(streamCount); |
812 |
} |
} |
813 |
|
|
814 |
/** |
/** |
834 |
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); |
835 |
if (itNewVoice) { |
if (itNewVoice) { |
836 |
itNewVoice->Render(Samples); |
itNewVoice->Render(Samples); |
837 |
if (itNewVoice->IsActive()) ActiveVoiceCountTemp++; // still active |
if (itNewVoice->IsActive()) { // still active |
838 |
else { // voice reached end, is now inactive |
*(pEngineChannel->pDimRegionsInUse->allocAppend()) = itNewVoice->pDimRgn; |
839 |
|
ActiveVoiceCountTemp++; |
840 |
|
pEngineChannel->SetVoiceCount(pEngineChannel->GetVoiceCount() + 1); |
841 |
|
|
842 |
|
if (itNewVoice->PlaybackState == Voice::playback_state_disk) { |
843 |
|
if (itNewVoice->DiskStreamRef.State != Stream::state_unused) { |
844 |
|
pEngineChannel->SetDiskStreamCount(pEngineChannel->GetDiskStreamCount() + 1); |
845 |
|
} |
846 |
|
} |
847 |
|
} else { // voice reached end, is now inactive |
848 |
FreeVoice(pEngineChannel, itNewVoice); // remove voice from the list of active voices |
FreeVoice(pEngineChannel, itNewVoice); // remove voice from the list of active voices |
849 |
} |
} |
850 |
} |
} |
871 |
* this audio fragment cycle |
* this audio fragment cycle |
872 |
*/ |
*/ |
873 |
void Engine::RouteAudio(EngineChannel* pEngineChannel, uint Samples) { |
void Engine::RouteAudio(EngineChannel* pEngineChannel, uint Samples) { |
874 |
// route master signal |
// route dry signal |
875 |
{ |
{ |
876 |
AudioChannel* pDstL = pAudioOutputDevice->Channel(pEngineChannel->AudioDeviceChannelLeft); |
AudioChannel* pDstL = pAudioOutputDevice->Channel(pEngineChannel->AudioDeviceChannelLeft); |
877 |
AudioChannel* pDstR = pAudioOutputDevice->Channel(pEngineChannel->AudioDeviceChannelRight); |
AudioChannel* pDstR = pAudioOutputDevice->Channel(pEngineChannel->AudioDeviceChannelRight); |
882 |
{ |
{ |
883 |
for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { |
for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { |
884 |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
885 |
// left channel |
for (int iChan = 0; iChan < 2; ++iChan) { |
886 |
const int iDstL = pFxSend->DestinationChannel(0); |
AudioChannel* pSource = |
887 |
if (iDstL < 0) { |
(iChan) |
888 |
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (L) destination channel")); |
? pEngineChannel->pChannelRight |
889 |
} else { |
: pEngineChannel->pChannelLeft; |
890 |
AudioChannel* pDstL = pAudioOutputDevice->Channel(iDstL); |
const int iDstChan = pFxSend->DestinationChannel(iChan); |
891 |
if (!pDstL) { |
if (iDstChan < 0) { |
892 |
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (L) destination channel")); |
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination channel (%d->%d)", ((iChan) ? "R" : "L"), iChan, iDstChan)); |
893 |
} else pEngineChannel->pChannelLeft->MixTo(pDstL, Samples, pFxSend->Level()); |
goto channel_cleanup; |
894 |
} |
} |
895 |
// right channel |
AudioChannel* pDstChan = NULL; |
896 |
const int iDstR = pFxSend->DestinationChannel(1); |
if (pFxSend->DestinationMasterEffectChain() >= 0) { // fx send routed to an internal master effect |
897 |
if (iDstR < 0) { |
EffectChain* pEffectChain = |
898 |
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (R) destination channel")); |
pAudioOutputDevice->MasterEffectChain( |
899 |
} else { |
pFxSend->DestinationMasterEffectChain() |
900 |
AudioChannel* pDstR = pAudioOutputDevice->Channel(iDstR); |
); |
901 |
if (!pDstR) { |
if (!pEffectChain) { |
902 |
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (R) destination channel")); |
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination effect chain %d", ((iChan) ? "R" : "L"), pFxSend->DestinationMasterEffectChain())); |
903 |
} else pEngineChannel->pChannelRight->MixTo(pDstR, Samples, pFxSend->Level()); |
goto channel_cleanup; |
904 |
|
} |
905 |
|
Effect* pEffect = |
906 |
|
pEffectChain->GetEffect( |
907 |
|
pFxSend->DestinationMasterEffect() |
908 |
|
); |
909 |
|
if (!pEffect) { |
910 |
|
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination effect %d of effect chain %d", ((iChan) ? "R" : "L"), pFxSend->DestinationMasterEffect(), pFxSend->DestinationMasterEffectChain())); |
911 |
|
goto channel_cleanup; |
912 |
|
} |
913 |
|
pDstChan = pEffect->InputChannel(iDstChan); |
914 |
|
} else { // FX send routed directly to an audio output channel |
915 |
|
pDstChan = pAudioOutputDevice->Channel(iDstChan); |
916 |
|
} |
917 |
|
if (!pDstChan) { |
918 |
|
dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination channel (%d->%d)", ((iChan) ? "R" : "L"), iChan, iDstChan)); |
919 |
|
goto channel_cleanup; |
920 |
|
} |
921 |
|
pSource->MixTo(pDstChan, Samples, pFxSend->Level()); |
922 |
} |
} |
923 |
} |
} |
924 |
} |
} |
925 |
|
channel_cleanup: |
926 |
// reset buffers with silence (zero out) for the next audio cycle |
// reset buffers with silence (zero out) for the next audio cycle |
927 |
pEngineChannel->pChannelLeft->Clear(); |
pEngineChannel->pChannelLeft->Clear(); |
928 |
pEngineChannel->pChannelRight->Clear(); |
pEngineChannel->pChannelRight->Clear(); |
968 |
* |
* |
969 |
* @param pData - pointer to sysex data |
* @param pData - pointer to sysex data |
970 |
* @param Size - lenght of sysex data (in bytes) |
* @param Size - lenght of sysex data (in bytes) |
971 |
|
* @param pSender - the MIDI input port on which the SysEx message was |
972 |
|
* received |
973 |
*/ |
*/ |
974 |
void Engine::SendSysex(void* pData, uint Size) { |
void Engine::SendSysex(void* pData, uint Size, MidiInputPort* pSender) { |
975 |
Event event = pEventGenerator->CreateEvent(); |
Event event = pEventGenerator->CreateEvent(); |
976 |
event.Type = Event::type_sysex; |
event.Type = Event::type_sysex; |
977 |
event.Param.Sysex.Size = Size; |
event.Param.Sysex.Size = Size; |
978 |
event.pEngineChannel = NULL; // as Engine global event |
event.pEngineChannel = NULL; // as Engine global event |
979 |
|
event.pMidiInputPort = pSender; |
980 |
if (pEventQueue->write_space() > 0) { |
if (pEventQueue->write_space() > 0) { |
981 |
if (pSysexBuffer->write_space() >= Size) { |
if (pSysexBuffer->write_space() >= Size) { |
982 |
// copy sysex data to input buffer |
// copy sysex data to input buffer |
1070 |
{ |
{ |
1071 |
// first, get total amount of required voices (dependant on amount of layers) |
// first, get total amount of required voices (dependant on amount of layers) |
1072 |
::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOnEventOnKeyList->Param.Note.Key); |
::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOnEventOnKeyList->Param.Note.Key); |
1073 |
if (pRegion) { |
if (pRegion && !RegionSuspended(pRegion)) { |
1074 |
int voicesRequired = pRegion->Layers; |
int voicesRequired = pRegion->Layers; |
1075 |
// now launch the required amount of voices |
// now launch the required amount of voices |
1076 |
for (int i = 0; i < voicesRequired; i++) |
for (int i = 0; i < voicesRequired; i++) |
1654 |
void Engine::ProcessControlChange(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itControlChangeEvent) { |
void Engine::ProcessControlChange(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itControlChangeEvent) { |
1655 |
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)); |
1656 |
|
|
1657 |
|
// handle the "control triggered" MIDI rule: a control change |
1658 |
|
// event can trigger a new note on or note off event |
1659 |
|
if (pEngineChannel->pInstrument) { |
1660 |
|
|
1661 |
|
::gig::MidiRule* rule; |
1662 |
|
for (int i = 0 ; (rule = pEngineChannel->pInstrument->GetMidiRule(i)) ; i++) { |
1663 |
|
|
1664 |
|
if (::gig::MidiRuleCtrlTrigger* ctrlTrigger = |
1665 |
|
dynamic_cast< ::gig::MidiRuleCtrlTrigger*>(rule)) { |
1666 |
|
if (itControlChangeEvent->Param.CC.Controller == |
1667 |
|
ctrlTrigger->ControllerNumber) { |
1668 |
|
|
1669 |
|
uint8_t oldCCValue = pEngineChannel->ControllerTable[ |
1670 |
|
itControlChangeEvent->Param.CC.Controller]; |
1671 |
|
uint8_t newCCValue = itControlChangeEvent->Param.CC.Value; |
1672 |
|
|
1673 |
|
for (int i = 0 ; i < ctrlTrigger->Triggers ; i++) { |
1674 |
|
::gig::MidiRuleCtrlTrigger::trigger_t* pTrigger = |
1675 |
|
&ctrlTrigger->pTriggers[i]; |
1676 |
|
|
1677 |
|
// check if the controller has passed the |
1678 |
|
// trigger point in the right direction |
1679 |
|
if ((pTrigger->Descending && |
1680 |
|
oldCCValue > pTrigger->TriggerPoint && |
1681 |
|
newCCValue <= pTrigger->TriggerPoint) || |
1682 |
|
(!pTrigger->Descending && |
1683 |
|
oldCCValue < pTrigger->TriggerPoint && |
1684 |
|
newCCValue >= pTrigger->TriggerPoint)) { |
1685 |
|
|
1686 |
|
RTList<Event>::Iterator itNewEvent = pGlobalEvents->allocAppend(); |
1687 |
|
if (itNewEvent) { |
1688 |
|
*itNewEvent = *itControlChangeEvent; |
1689 |
|
itNewEvent->Param.Note.Key = pTrigger->Key; |
1690 |
|
|
1691 |
|
if (pTrigger->NoteOff || pTrigger->Velocity == 0) { |
1692 |
|
itNewEvent->Type = Event::type_note_off; |
1693 |
|
itNewEvent->Param.Note.Velocity = 100; |
1694 |
|
|
1695 |
|
ProcessNoteOff(pEngineChannel, itNewEvent); |
1696 |
|
} else { |
1697 |
|
itNewEvent->Type = Event::type_note_on; |
1698 |
|
//TODO: if Velocity is 255, the triggered velocity should |
1699 |
|
// depend on how fast the controller is moving |
1700 |
|
itNewEvent->Param.Note.Velocity = |
1701 |
|
pTrigger->Velocity == 255 ? 100 : |
1702 |
|
pTrigger->Velocity; |
1703 |
|
|
1704 |
|
ProcessNoteOn(pEngineChannel, itNewEvent); |
1705 |
|
} |
1706 |
|
} |
1707 |
|
else dmsg(1,("Event pool emtpy!\n")); |
1708 |
|
} |
1709 |
|
} |
1710 |
|
} |
1711 |
|
} |
1712 |
|
} |
1713 |
|
} |
1714 |
|
|
1715 |
// update controller value in the engine channel's controller table |
// update controller value in the engine channel's controller table |
1716 |
pEngineChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; |
pEngineChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; |
1717 |
|
|
1728 |
transpose = RTMath::Min(transpose, 24); |
transpose = RTMath::Min(transpose, 24); |
1729 |
transpose = RTMath::Max(transpose, -24); |
transpose = RTMath::Max(transpose, -24); |
1730 |
pEngineChannel->GlobalTranspose = transpose; |
pEngineChannel->GlobalTranspose = transpose; |
1731 |
|
// workaround, so we won't have hanging notes |
1732 |
|
ReleaseAllVoices(pEngineChannel, itControlChangeEvent); |
1733 |
} |
} |
1734 |
|
// to avoid other MIDI CC #6 messages to be misenterpreted as RPN controller data |
1735 |
|
pEngineChannel->ResetMidiRpnController(); |
1736 |
break; |
break; |
1737 |
} |
} |
1738 |
case 7: { // volume |
case 7: { // volume |
1745 |
//TODO: not sample accurate yet |
//TODO: not sample accurate yet |
1746 |
pEngineChannel->GlobalPanLeft = PanCurve[128 - itControlChangeEvent->Param.CC.Value]; |
pEngineChannel->GlobalPanLeft = PanCurve[128 - itControlChangeEvent->Param.CC.Value]; |
1747 |
pEngineChannel->GlobalPanRight = PanCurve[itControlChangeEvent->Param.CC.Value]; |
pEngineChannel->GlobalPanRight = PanCurve[itControlChangeEvent->Param.CC.Value]; |
1748 |
|
pEngineChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value; |
1749 |
break; |
break; |
1750 |
} |
} |
1751 |
case 64: { // sustain |
case 64: { // sustain |
1796 |
break; |
break; |
1797 |
} |
} |
1798 |
case 65: { // portamento on / off |
case 65: { // portamento on / off |
1799 |
KillAllVoices(pEngineChannel, itControlChangeEvent); |
const bool bPortamento = itControlChangeEvent->Param.CC.Value >= 64; |
1800 |
pEngineChannel->PortamentoMode = itControlChangeEvent->Param.CC.Value >= 64; |
if (bPortamento != pEngineChannel->PortamentoMode) |
1801 |
|
KillAllVoices(pEngineChannel, itControlChangeEvent); |
1802 |
|
pEngineChannel->PortamentoMode = bPortamento; |
1803 |
break; |
break; |
1804 |
} |
} |
1805 |
case 66: { // sostenuto |
case 66: { // sostenuto |
1869 |
break; |
break; |
1870 |
} |
} |
1871 |
case 126: { // mono mode on |
case 126: { // mono mode on |
1872 |
KillAllVoices(pEngineChannel, itControlChangeEvent); |
if (!pEngineChannel->SoloMode) |
1873 |
|
KillAllVoices(pEngineChannel, itControlChangeEvent); |
1874 |
pEngineChannel->SoloMode = true; |
pEngineChannel->SoloMode = true; |
1875 |
break; |
break; |
1876 |
} |
} |
1877 |
case 127: { // poly mode on |
case 127: { // poly mode on |
1878 |
KillAllVoices(pEngineChannel, itControlChangeEvent); |
if (pEngineChannel->SoloMode) |
1879 |
|
KillAllVoices(pEngineChannel, itControlChangeEvent); |
1880 |
pEngineChannel->SoloMode = false; |
pEngineChannel->SoloMode = false; |
1881 |
break; |
break; |
1882 |
} |
} |
1886 |
if (!pEngineChannel->fxSends.empty()) { |
if (!pEngineChannel->fxSends.empty()) { |
1887 |
for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { |
for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { |
1888 |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); |
1889 |
if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) |
if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) { |
1890 |
pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); |
pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); |
1891 |
|
pFxSend->SetInfoChanged(true); |
1892 |
|
} |
1893 |
} |
} |
1894 |
} |
} |
1895 |
} |
} |
1908 |
if (exclusive_status != 0xF0) goto free_sysex_data; |
if (exclusive_status != 0xF0) goto free_sysex_data; |
1909 |
|
|
1910 |
switch (id) { |
switch (id) { |
1911 |
|
case 0x7f: { // (Realtime) Universal Sysex (GM Standard) |
1912 |
|
uint8_t sysex_channel, sub_id1, sub_id2, val_msb, val_lsb;; |
1913 |
|
if (!reader.pop(&sysex_channel)) goto free_sysex_data; |
1914 |
|
if (!reader.pop(&sub_id1)) goto free_sysex_data; |
1915 |
|
if (!reader.pop(&sub_id2)) goto free_sysex_data; |
1916 |
|
if (!reader.pop(&val_lsb)) goto free_sysex_data; |
1917 |
|
if (!reader.pop(&val_msb)) goto free_sysex_data; |
1918 |
|
//TODO: for now we simply ignore the sysex channel, seldom used anyway |
1919 |
|
switch (sub_id1) { |
1920 |
|
case 0x04: // Device Control |
1921 |
|
switch (sub_id2) { |
1922 |
|
case 0x01: { // Master Volume |
1923 |
|
const double volume = |
1924 |
|
double((uint(val_msb)<<7) | uint(val_lsb)) / 16383.0; |
1925 |
|
#if CONFIG_MASTER_VOLUME_SYSEX_BY_PORT |
1926 |
|
// apply volume to all sampler channels that |
1927 |
|
// are connected to the same MIDI input port |
1928 |
|
// this sysex message arrived on |
1929 |
|
for (int i = 0; i < engineChannels.size(); ++i) { |
1930 |
|
EngineChannel* pEngineChannel = engineChannels[i]; |
1931 |
|
if (pEngineChannel->GetMidiInputPort() == |
1932 |
|
itSysexEvent->pMidiInputPort) |
1933 |
|
{ |
1934 |
|
pEngineChannel->Volume(volume); |
1935 |
|
} |
1936 |
|
} |
1937 |
|
#else |
1938 |
|
// apply volume globally to the whole sampler |
1939 |
|
GLOBAL_VOLUME = volume; |
1940 |
|
#endif // CONFIG_MASTER_VOLUME_SYSEX_BY_PORT |
1941 |
|
break; |
1942 |
|
} |
1943 |
|
} |
1944 |
|
break; |
1945 |
|
} |
1946 |
|
break; |
1947 |
|
} |
1948 |
case 0x41: { // Roland |
case 0x41: { // Roland |
1949 |
dmsg(3,("Roland Sysex\n")); |
dmsg(3,("Roland Sysex\n")); |
1950 |
uint8_t device_id, model_id, cmd_id; |
uint8_t device_id, model_id, cmd_id; |
1960 |
if (reader.read(&addr[0], 3) != 3) goto free_sysex_data; |
if (reader.read(&addr[0], 3) != 3) goto free_sysex_data; |
1961 |
if (addr[0] == 0x40 && addr[1] == 0x00) { // System Parameters |
if (addr[0] == 0x40 && addr[1] == 0x00) { // System Parameters |
1962 |
dmsg(3,("\tSystem Parameter\n")); |
dmsg(3,("\tSystem Parameter\n")); |
1963 |
|
if (addr[2] == 0x7f) { // GS reset |
1964 |
|
for (int i = 0; i < engineChannels.size(); ++i) { |
1965 |
|
EngineChannel* pEngineChannel = engineChannels[i]; |
1966 |
|
if (pEngineChannel->GetMidiInputPort() == itSysexEvent->pMidiInputPort) { |
1967 |
|
KillAllVoices(pEngineChannel, itSysexEvent); |
1968 |
|
pEngineChannel->ResetControllers(); |
1969 |
|
} |
1970 |
|
} |
1971 |
|
} |
1972 |
} |
} |
1973 |
else if (addr[0] == 0x40 && addr[1] == 0x01) { // Common Parameters |
else if (addr[0] == 0x40 && addr[1] == 0x01) { // Common Parameters |
1974 |
dmsg(3,("\tCommon Parameter\n")); |
dmsg(3,("\tCommon Parameter\n")); |
1990 |
dmsg(3,("\t\t\tNew scale applied.\n")); |
dmsg(3,("\t\t\tNew scale applied.\n")); |
1991 |
break; |
break; |
1992 |
} |
} |
1993 |
|
case 0x15: { // chromatic / drumkit mode |
1994 |
|
dmsg(3,("\t\tMIDI Instrument Map Switch\n")); |
1995 |
|
uint8_t part = addr[1] & 0x0f; |
1996 |
|
uint8_t map; |
1997 |
|
if (!reader.pop(&map)) goto free_sysex_data; |
1998 |
|
for (int i = 0; i < engineChannels.size(); ++i) { |
1999 |
|
EngineChannel* pEngineChannel = engineChannels[i]; |
2000 |
|
if ( |
2001 |
|
(pEngineChannel->midiChannel == part || |
2002 |
|
pEngineChannel->midiChannel == midi_chan_all) && |
2003 |
|
pEngineChannel->GetMidiInputPort() == itSysexEvent->pMidiInputPort |
2004 |
|
) { |
2005 |
|
try { |
2006 |
|
pEngineChannel->SetMidiInstrumentMap(map); |
2007 |
|
} catch (Exception e) { |
2008 |
|
dmsg(2,("\t\t\tCould not apply MIDI instrument map %d to part %d: %s\n", map, part, e.Message().c_str())); |
2009 |
|
goto free_sysex_data; |
2010 |
|
} catch (...) { |
2011 |
|
dmsg(2,("\t\t\tCould not apply MIDI instrument map %d to part %d (unknown exception)\n", map, part)); |
2012 |
|
goto free_sysex_data; |
2013 |
|
} |
2014 |
|
} |
2015 |
|
} |
2016 |
|
dmsg(3,("\t\t\tApplied MIDI instrument map %d to part %d.\n", map, part)); |
2017 |
|
break; |
2018 |
|
} |
2019 |
} |
} |
2020 |
} |
} |
2021 |
else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x20) { // Part Parameters (2) |
else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x20) { // Part Parameters (2) |
2122 |
} |
} |
2123 |
|
|
2124 |
uint Engine::VoiceCount() { |
uint Engine::VoiceCount() { |
2125 |
return ActiveVoiceCount; |
return atomic_read(&ActiveVoiceCount); |
2126 |
|
} |
2127 |
|
|
2128 |
|
void Engine::SetVoiceCount(uint Count) { |
2129 |
|
atomic_set(&ActiveVoiceCount, Count); |
2130 |
} |
} |
2131 |
|
|
2132 |
uint Engine::VoiceCountMax() { |
uint Engine::VoiceCountMax() { |
2133 |
return ActiveVoiceCountMax; |
return ActiveVoiceCountMax; |
2134 |
} |
} |
2135 |
|
|
2136 |
|
int Engine::MaxVoices() { |
2137 |
|
return pVoicePool->poolSize(); |
2138 |
|
} |
2139 |
|
|
2140 |
|
void Engine::SetMaxVoices(int iVoices) throw (Exception) { |
2141 |
|
if (iVoices < 1) |
2142 |
|
throw Exception("Maximum voices for an engine cannot be set lower than 1"); |
2143 |
|
|
2144 |
|
SuspendAll(); |
2145 |
|
|
2146 |
|
// NOTE: we need to clear pDimRegionsInUse before deleting pDimRegionPool, |
2147 |
|
// otherwise memory corruption will occur if there are active voices (see bug #118) |
2148 |
|
for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { |
2149 |
|
engineChannels[iChannel]->ClearDimRegionsInUse(); |
2150 |
|
} |
2151 |
|
|
2152 |
|
if (pDimRegionPool[0]) delete pDimRegionPool[0]; |
2153 |
|
if (pDimRegionPool[1]) delete pDimRegionPool[1]; |
2154 |
|
|
2155 |
|
pDimRegionPool[0] = new Pool< ::gig::DimensionRegion*>(iVoices); |
2156 |
|
pDimRegionPool[1] = new Pool< ::gig::DimensionRegion*>(iVoices); |
2157 |
|
|
2158 |
|
for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { |
2159 |
|
engineChannels[iChannel]->ResetDimRegionsInUse(); |
2160 |
|
} |
2161 |
|
|
2162 |
|
try { |
2163 |
|
pVoicePool->resizePool(iVoices); |
2164 |
|
} catch (...) { |
2165 |
|
throw Exception("FATAL: Could not resize voice pool!"); |
2166 |
|
} |
2167 |
|
|
2168 |
|
for (RTList<Voice>::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
2169 |
|
iterVoice->SetEngine(this); |
2170 |
|
iterVoice->pDiskThread = this->pDiskThread; |
2171 |
|
} |
2172 |
|
pVoicePool->clear(); |
2173 |
|
|
2174 |
|
ResumeAll(); |
2175 |
|
} |
2176 |
|
|
2177 |
bool Engine::DiskStreamSupported() { |
bool Engine::DiskStreamSupported() { |
2178 |
return true; |
return true; |
2179 |
} |
} |
2180 |
|
|
2181 |
uint Engine::DiskStreamCount() { |
uint Engine::DiskStreamCount() { |
2182 |
return (pDiskThread) ? pDiskThread->ActiveStreamCount : 0; |
return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; |
2183 |
} |
} |
2184 |
|
|
2185 |
uint Engine::DiskStreamCountMax() { |
uint Engine::DiskStreamCountMax() { |
2186 |
return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; |
return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; |
2187 |
} |
} |
2188 |
|
|
2189 |
|
int Engine::MaxDiskStreams() { |
2190 |
|
return iMaxDiskStreams; |
2191 |
|
} |
2192 |
|
|
2193 |
|
void Engine::SetMaxDiskStreams(int iStreams) throw (Exception) { |
2194 |
|
if (iStreams < 0) |
2195 |
|
throw Exception("Maximum disk streams for an engine cannot be set lower than 0"); |
2196 |
|
|
2197 |
|
SuspendAll(); |
2198 |
|
|
2199 |
|
iMaxDiskStreams = iStreams; |
2200 |
|
|
2201 |
|
// reconnect to audio output device, because that will automatically |
2202 |
|
// recreate the disk thread with the required amount of streams |
2203 |
|
if (pAudioOutputDevice) Connect(pAudioOutputDevice); |
2204 |
|
|
2205 |
|
ResumeAll(); |
2206 |
|
} |
2207 |
|
|
2208 |
String Engine::DiskStreamBufferFillBytes() { |
String Engine::DiskStreamBufferFillBytes() { |
2209 |
return pDiskThread->GetBufferFillBytes(); |
return pDiskThread->GetBufferFillBytes(); |
2210 |
} |
} |
2218 |
} |
} |
2219 |
|
|
2220 |
String Engine::Description() { |
String Engine::Description() { |
2221 |
return "Gigasampler Engine"; |
return "Gigasampler Format Engine"; |
2222 |
} |
} |
2223 |
|
|
2224 |
String Engine::Version() { |
String Engine::Version() { |
2225 |
String s = "$Revision: 1.73 $"; |
String s = "$Revision: 1.102 $"; |
2226 |
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 |
2227 |
} |
} |
2228 |
|
|
2231 |
} |
} |
2232 |
|
|
2233 |
// static constant initializers |
// static constant initializers |
2234 |
const float* Engine::VolumeCurve(InitVolumeCurve()); |
const Engine::FloatTable Engine::VolumeCurve(InitVolumeCurve()); |
2235 |
const float* Engine::PanCurve(InitPanCurve()); |
const Engine::FloatTable Engine::PanCurve(InitPanCurve()); |
2236 |
const float* Engine::CrossfadeCurve(InitCrossfadeCurve()); |
const Engine::FloatTable Engine::CrossfadeCurve(InitCrossfadeCurve()); |
2237 |
|
|
2238 |
float* Engine::InitVolumeCurve() { |
float* Engine::InitVolumeCurve() { |
2239 |
// line-segment approximation |
// line-segment approximation |
2272 |
return y; |
return y; |
2273 |
} |
} |
2274 |
|
|
|
/** |
|
|
* 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; |
|
|
} |
|
|
|
|
2275 |
}} // namespace LinuxSampler::gig |
}} // namespace LinuxSampler::gig |