/[svn]/linuxsampler/trunk/src/engines/EngineBase.h
ViewVC logotype

Contents of /linuxsampler/trunk/src/engines/EngineBase.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2598 - (show annotations) (download) (as text)
Fri Jun 6 12:38:54 2014 UTC (9 years, 10 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 87401 byte(s)
* ScriptVM (WIP): Built-in script function "play_note()" now returns the
  event ID of the triggered note.
* ScriptVM (WIP): Implemented built-in script int variable $EVENT_ID.
* ScriptVM (WIP): Implemented built-in script function "ignore_event()".
* ScriptVM (WIP): Implemented built-in script function
  "ignore_controller()" (accepts one and no argument).
* Bumped version (1.0.0.svn44).

1 /***************************************************************************
2 * *
3 * LinuxSampler - modular, streaming capable sampler *
4 * *
5 * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck *
6 * Copyright (C) 2005-2008 Christian Schoenebeck *
7 * Copyright (C) 2009-2013 Christian Schoenebeck and Grigor Iliev *
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 * This program is distributed in the hope that it will be useful, *
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17 * GNU General Public License for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License *
20 * along with this program; if not, write to the Free Software *
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
22 * MA 02111-1307 USA *
23 ***************************************************************************/
24
25 #ifndef __LS_ENGINEBASE_H__
26 #define __LS_ENGINEBASE_H__
27
28 #include "AbstractEngine.h"
29 #include "EngineChannelBase.h"
30 #include "common/DiskThreadBase.h"
31 #include "common/MidiKeyboardManager.h"
32 #include "InstrumentManager.h"
33 #include "../common/global_private.h"
34
35
36 namespace LinuxSampler {
37
38 class AbstractEngineChannel;
39
40 template <
41 class V /* Voice */,
42 class RR /* Root Region */,
43 class R /* Region */,
44 class D /* Disk Thread */,
45 class IM /* Instrument Manager */,
46 class I /* Instrument */
47 >
48 class EngineBase: public AbstractEngine, public RegionPools<R>, public VoicePool<V> {
49
50 public:
51 typedef typename RTList<V>::Iterator VoiceIterator;
52 typedef typename Pool<V>::Iterator PoolVoiceIterator;
53 typedef typename RTList<RR*>::Iterator RootRegionIterator;
54 typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;
55
56 EngineBase() : SuspendedRegions(128) {
57 pDiskThread = NULL;
58 pVoicePool = new Pool<V>(GLOBAL_MAX_VOICES);
59 pRegionPool[0] = new Pool<R*>(GLOBAL_MAX_VOICES);
60 pRegionPool[1] = new Pool<R*>(GLOBAL_MAX_VOICES);
61 pVoiceStealingQueue = new RTList<Event>(pEventPool);
62 iMaxDiskStreams = GLOBAL_MAX_STREAMS;
63
64 for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
65 iterVoice->SetEngine(this);
66 }
67 pVoicePool->clear();
68
69 ResetInternal();
70 ResetScaleTuning();
71 ResetSuspendedRegions();
72 }
73
74 virtual ~EngineBase() {
75 if (pDiskThread) {
76 dmsg(1,("Stopping disk thread..."));
77 pDiskThread->StopThread();
78 delete pDiskThread;
79 dmsg(1,("OK\n"));
80 }
81
82 if (pVoicePool) {
83 pVoicePool->clear();
84 delete pVoicePool;
85 }
86
87 if (pVoiceStealingQueue) delete pVoiceStealingQueue;
88
89 if (pRegionPool[0]) delete pRegionPool[0];
90 if (pRegionPool[1]) delete pRegionPool[1];
91 ResetSuspendedRegions();
92 }
93
94 // implementation of abstract methods derived from class 'LinuxSampler::Engine'
95
96 /**
97 * Let this engine proceed to render the given amount of sample points.
98 * The engine will iterate through all engine channels and render audio
99 * for each engine channel independently. The calculated audio data of
100 * all voices of each engine channel will be placed into the audio sum
101 * buffers of the respective audio output device, connected to the
102 * respective engine channel.
103 *
104 * @param Samples - number of sample points to be rendered
105 * @returns 0 on success
106 */
107 virtual int RenderAudio(uint Samples) OVERRIDE {
108 dmsg(8,("RenderAudio(Samples=%d)\n", Samples));
109
110 // return if engine disabled
111 if (EngineDisabled.Pop()) {
112 dmsg(5,("EngineBase: engine disabled (val=%d)\n",EngineDisabled.GetUnsafe()));
113 EngineDisabled.RttDone();
114 return 0;
115 }
116
117 // process requests for suspending / resuming regions (i.e. to avoid
118 // crashes while these regions are modified by an instrument editor)
119 ProcessSuspensionsChanges();
120
121 // update time of start and end of this audio fragment (as events' time stamps relate to this)
122 pEventGenerator->UpdateFragmentTime(Samples);
123
124 // We only allow the given maximum number of voices to be spawned
125 // in each audio fragment. All subsequent request for spawning new
126 // voices in the same audio fragment will be ignored.
127 VoiceSpawnsLeft = MaxVoices();
128
129 // get all events from the engine's global input event queue which belong to the current fragment
130 // (these are usually just SysEx messages)
131 ImportEvents(Samples);
132
133 // process engine global events (these are currently only MIDI System Exclusive messages)
134 {
135 RTList<Event>::Iterator itEvent = pGlobalEvents->first();
136 RTList<Event>::Iterator end = pGlobalEvents->end();
137 for (; itEvent != end; ++itEvent) {
138 switch (itEvent->Type) {
139 case Event::type_sysex:
140 dmsg(5,("Engine: Sysex received\n"));
141 ProcessSysex(itEvent);
142 break;
143 }
144 }
145 }
146
147 // In case scale tuning has been changed, recalculate pitch for
148 // all active voices.
149 ProcessScaleTuningChange();
150
151 // reset internal voice counter (just for statistic of active voices)
152 ActiveVoiceCountTemp = 0;
153
154 HandleInstrumentChanges();
155
156 // handle events on all engine channels
157 for (int i = 0; i < engineChannels.size(); i++) {
158 ProcessEvents(engineChannels[i], Samples);
159 }
160
161 // render all 'normal', active voices on all engine channels
162 for (int i = 0; i < engineChannels.size(); i++) {
163 RenderActiveVoices(engineChannels[i], Samples);
164 }
165
166 // now that all ordinary voices on ALL engine channels are rendered, render new stolen voices
167 RenderStolenVoices(Samples);
168
169 // handle audio routing for engine channels with FX sends
170 for (int i = 0; i < engineChannels.size(); i++) {
171 AbstractEngineChannel* pChannel = static_cast<AbstractEngineChannel*>(engineChannels[i]);
172 if (pChannel->fxSends.empty()) continue; // ignore if no FX sends
173 RouteAudio(engineChannels[i], Samples);
174 }
175
176 // handle cleanup on all engine channels for the next audio fragment
177 for (int i = 0; i < engineChannels.size(); i++) {
178 PostProcess(engineChannels[i]);
179 }
180
181
182 // empty the engine's event list for the next audio fragment
183 ClearEventLists();
184
185 // reset voice stealing for the next audio fragment
186 pVoiceStealingQueue->clear();
187
188 // just some statistics about this engine instance
189 SetVoiceCount(ActiveVoiceCountTemp);
190 if (VoiceCount() > ActiveVoiceCountMax) ActiveVoiceCountMax = VoiceCount();
191
192 // in case regions were previously suspended and we killed voices
193 // with disk streams due to that, check if those streams have finally
194 // been deleted by the disk thread
195 if (iPendingStreamDeletions) ProcessPendingStreamDeletions();
196
197 // Release the instrument change command. (This has to
198 // be done after all voices have been rendered and not
199 // in HandleInstrumentChanges, as the RegionsInUse
200 // list has been built up by the voice renderers.)
201 for (int i = 0; i < engineChannels.size(); i++) {
202 EngineChannelBase<V, R, I>* channel =
203 static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]);
204 channel->InstrumentChangeCommandReader.Unlock();
205 }
206 FrameTime += Samples;
207
208 EngineDisabled.RttDone();
209 return 0;
210 }
211
212 virtual int MaxVoices() OVERRIDE { return pVoicePool->poolSize(); }
213
214 virtual void SetMaxVoices(int iVoices) throw (Exception) OVERRIDE {
215 if (iVoices < 1)
216 throw Exception("Maximum voices for an engine cannot be set lower than 1");
217
218 SuspendAll();
219
220 // NOTE: we need to clear pRegionsInUse before deleting pDimRegionPool,
221 // otherwise memory corruption will occur if there are active voices (see bug #118)
222 for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) {
223 EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(engineChannels[iChannel]);
224 pChannel->ClearRegionsInUse();
225 }
226
227 if (pRegionPool[0]) delete pRegionPool[0];
228 if (pRegionPool[1]) delete pRegionPool[1];
229
230 pRegionPool[0] = new Pool<R*>(iVoices);
231 pRegionPool[1] = new Pool<R*>(iVoices);
232
233 for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) {
234 EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(engineChannels[iChannel]);
235 pChannel->ResetRegionsInUse(pRegionPool);
236 }
237
238 try {
239 pVoicePool->resizePool(iVoices);
240 } catch (...) {
241 throw Exception("FATAL: Could not resize voice pool!");
242 }
243
244 for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
245 iterVoice->SetEngine(this);
246 iterVoice->pDiskThread = this->pDiskThread;
247 }
248 pVoicePool->clear();
249
250 PostSetMaxVoices(iVoices);
251 ResumeAll();
252 }
253
254 /** Called after the new max number of voices is set and before resuming the engine. */
255 virtual void PostSetMaxVoices(int iVoices) { }
256
257 virtual uint DiskStreamCount() OVERRIDE { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; }
258 virtual uint DiskStreamCountMax() OVERRIDE { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; }
259 virtual int MaxDiskStreams() OVERRIDE { return iMaxDiskStreams; }
260
261 virtual void SetMaxDiskStreams(int iStreams) throw (Exception) OVERRIDE {
262 if (iStreams < 0)
263 throw Exception("Maximum disk streams for an engine cannot be set lower than 0");
264
265 SuspendAll();
266
267 iMaxDiskStreams = iStreams;
268
269 // reconnect to audio output device, because that will automatically
270 // recreate the disk thread with the required amount of streams
271 if (pAudioOutputDevice) Connect(pAudioOutputDevice);
272
273 ResumeAll();
274 }
275
276 virtual String DiskStreamBufferFillBytes() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; }
277 virtual String DiskStreamBufferFillPercentage() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; }
278 virtual InstrumentManager* GetInstrumentManager() OVERRIDE { return &instruments; }
279
280 /**
281 * Connect this engine instance with the given audio output device.
282 * This method will be called when an Engine instance is created.
283 * All of the engine's data structures which are dependant to the used
284 * audio output device / driver will be (re)allocated and / or
285 * adjusted appropriately.
286 *
287 * @param pAudioOut - audio output device to connect to
288 */
289 virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE {
290 // caution: don't ignore if connecting to the same device here,
291 // because otherwise SetMaxDiskStreams() implementation won't work anymore!
292
293 pAudioOutputDevice = pAudioOut;
294
295 ResetInternal();
296
297 // inform audio driver for the need of two channels
298 try {
299 pAudioOutputDevice->AcquireChannels(2); // default stereo
300 }
301 catch (AudioOutputException e) {
302 String msg = "Audio output device unable to provide 2 audio channels, cause: " + e.Message();
303 throw Exception(msg);
304 }
305
306 this->MaxSamplesPerCycle = pAudioOutputDevice->MaxSamplesPerCycle();
307 this->SampleRate = pAudioOutputDevice->SampleRate();
308
309 MinFadeOutSamples = int(double(SampleRate) * CONFIG_EG_MIN_RELEASE_TIME) - 1;
310 if (MaxSamplesPerCycle < MinFadeOutSamples) {
311 std::cerr << "EngineBase: WARNING, CONFIG_EG_MIN_RELEASE_TIME "
312 << "too big for current audio fragment size & sampling rate! "
313 << "May lead to click sounds if voice stealing chimes in!\n" << std::flush;
314 // force volume ramp downs at the beginning of each fragment
315 MinFadeOutSamples = MaxSamplesPerCycle;
316 // lower minimum release time
317 const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate;
318 for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
319 iterVoice->CalculateFadeOutCoeff(minReleaseTime, SampleRate);
320 }
321 pVoicePool->clear();
322 }
323
324 // (re)create disk thread
325 if (this->pDiskThread) {
326 dmsg(1,("Stopping disk thread..."));
327 this->pDiskThread->StopThread();
328 delete this->pDiskThread;
329 dmsg(1,("OK\n"));
330 }
331 this->pDiskThread = CreateDiskThread();
332
333 if (!pDiskThread) {
334 dmsg(0,("EngineBase new diskthread = NULL\n"));
335 exit(EXIT_FAILURE);
336 }
337
338 for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
339 iterVoice->pDiskThread = this->pDiskThread;
340 dmsg(3,("d"));
341 }
342 pVoicePool->clear();
343
344 // (re)create event generator
345 if (pEventGenerator) delete pEventGenerator;
346 pEventGenerator = new EventGenerator(pAudioOut->SampleRate());
347
348 dmsg(1,("Starting disk thread..."));
349 pDiskThread->StartThread();
350 dmsg(1,("OK\n"));
351
352 bool printEqInfo = true;
353 for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
354 if (!iterVoice->pDiskThread) {
355 dmsg(0,("Engine -> voice::trigger: !pDiskThread\n"));
356 exit(EXIT_FAILURE);
357 }
358
359 iterVoice->CreateEq();
360
361 if(printEqInfo) {
362 iterVoice->PrintEqInfo();
363 printEqInfo = false;
364 }
365 }
366 pVoicePool->clear();
367
368 // (re)create dedicated voice audio buffers
369 //TODO: we could optimize resource usage a bit by just allocating these dedicated voice buffers when there is at least one engine channel with FX sends, because only in this case those special buffers are used actually, but since it would usually only save couple bytes in total, its probably not worth it
370 if (pDedicatedVoiceChannelLeft) delete pDedicatedVoiceChannelLeft;
371 if (pDedicatedVoiceChannelRight) delete pDedicatedVoiceChannelRight;
372 pDedicatedVoiceChannelLeft = new AudioChannel(0, MaxSamplesPerCycle);
373 pDedicatedVoiceChannelRight = new AudioChannel(1, MaxSamplesPerCycle);
374 }
375
376 // Implementattion for abstract method derived from Engine.
377 virtual void ReconnectAudioOutputDevice() OVERRIDE {
378 SuspendAll();
379 if (pAudioOutputDevice) Connect(pAudioOutputDevice);
380 ResumeAll();
381 }
382
383 /**
384 * Similar to @c Disable() but this method additionally kills all voices
385 * and disk streams and blocks until all voices and disk streams are actually
386 * killed / deleted.
387 *
388 * @e Note: only the original calling thread is able to re-enable the
389 * engine afterwards by calling @c ResumeAll() later on!
390 */
391 virtual void SuspendAll() {
392 dmsg(2,("Engine: Suspending all ...\n"));
393 // stop the engine, so we can safely modify the engine's
394 // data structures from this foreign thread
395 DisableAndLock();
396 // we could also use the respective class member variable here,
397 // but this is probably safer and cleaner
398 int iPendingStreamDeletions = 0;
399 // kill all voices on all engine channels the *die hard* way
400 for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) {
401 EngineChannelBase<V, R, I>* pEngineChannel =
402 static_cast<EngineChannelBase<V, R, I>*>(engineChannels[iChannel]);
403
404 iPendingStreamDeletions += pEngineChannel->KillAllVoicesImmediately();
405 }
406 // wait until all streams were actually deleted by the disk thread
407 while (iPendingStreamDeletions) {
408 while (
409 iPendingStreamDeletions &&
410 pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE
411 ) iPendingStreamDeletions--;
412 if (!iPendingStreamDeletions) break;
413 usleep(10000); // sleep for 10ms
414 }
415 dmsg(2,("EngineBase: Everything suspended.\n"));
416 }
417
418 /**
419 * At the moment same as calling @c Enable() directly, but this might
420 * change in future, so better call this method as counterpart to
421 * @c SuspendAll() instead of @c Enable() !
422 */
423 virtual void ResumeAll() { Enable(); }
424
425 /**
426 * Order the engine to stop rendering audio for the given region.
427 * Additionally this method will block until all voices and their disk
428 * streams associated with that region are actually killed / deleted, so
429 * one can i.e. safely modify the region with an instrument editor after
430 * returning from this method.
431 *
432 * @param pRegion - region the engine shall stop using
433 */
434 virtual void Suspend(RR* pRegion) {
435 dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion));
436 {
437 LockGuard lock(SuspendedRegionsMutex);
438 SuspensionChangeOngoing.Set(true);
439 pPendingRegionSuspension = pRegion;
440 SuspensionChangeOngoing.WaitAndUnlockIf(true);
441 }
442 dmsg(2,("EngineBase: Region %x suspended.",pRegion));
443 }
444
445 /**
446 * Orders the engine to resume playing back the given region, previously
447 * suspended with @c Suspend() .
448 *
449 * @param pRegion - region the engine shall be allowed to use again
450 */
451 virtual void Resume(RR* pRegion) {
452 dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion));
453 {
454 LockGuard lock(SuspendedRegionsMutex);
455 SuspensionChangeOngoing.Set(true);
456 pPendingRegionResumption = pRegion;
457 SuspensionChangeOngoing.WaitAndUnlockIf(true);
458 }
459 dmsg(2,("EngineBase: Region %x resumed.\n",pRegion));
460 }
461
462 virtual void ResetSuspendedRegions() {
463 SuspendedRegions.clear();
464 iPendingStreamDeletions = 0;
465 pPendingRegionSuspension = pPendingRegionResumption = NULL;
466 SuspensionChangeOngoing.Set(false);
467 }
468
469 /**
470 * Called by the engine's (audio) thread once per cycle to process requests
471 * from the outer world to suspend or resume a given @c gig::Region .
472 */
473 virtual void ProcessSuspensionsChanges() {
474 // process request for suspending one region
475 if (pPendingRegionSuspension) {
476 // kill all voices on all engine channels that use this region
477 for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) {
478 EngineChannelBase<V, R, I>* pEngineChannel =
479 static_cast<EngineChannelBase<V, R, I>*>(engineChannels[iChannel]);
480 SuspensionVoiceHandler handler(pPendingRegionSuspension);
481 pEngineChannel->ProcessActiveVoices(&handler);
482 iPendingStreamDeletions += handler.PendingStreamDeletions;
483 }
484 // make sure the region is not yet on the list
485 bool bAlreadySuspended = false;
486 RootRegionIterator iter = SuspendedRegions.first();
487 RootRegionIterator end = SuspendedRegions.end();
488 for (; iter != end; ++iter) { // iterate through all suspended regions
489 if (*iter == pPendingRegionSuspension) { // found
490 bAlreadySuspended = true;
491 dmsg(1,("EngineBase: attempt to suspend an already suspended region !!!\n"));
492 break;
493 }
494 }
495 if (!bAlreadySuspended) {
496 // put the region on the list of suspended regions
497 RootRegionIterator iter = SuspendedRegions.allocAppend();
498 if (iter) {
499 *iter = pPendingRegionSuspension;
500 } else std::cerr << "EngineBase: Could not suspend Region, list is full. This is a bug!!!\n" << std::flush;
501 }
502 // free request slot for next caller (and to make sure that
503 // we're not going to process the same request in the next cycle)
504 pPendingRegionSuspension = NULL;
505 // if no disk stream deletions are pending, awaken other side, as
506 // we're done in this case
507 if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false);
508 }
509
510 // process request for resuming one region
511 if (pPendingRegionResumption) {
512 // remove region from the list of suspended regions
513 RootRegionIterator iter = SuspendedRegions.first();
514 RootRegionIterator end = SuspendedRegions.end();
515 for (; iter != end; ++iter) { // iterate through all suspended regions
516 if (*iter == pPendingRegionResumption) { // found
517 SuspendedRegions.free(iter);
518 break; // done
519 }
520 }
521 // free request slot for next caller
522 pPendingRegionResumption = NULL;
523 // awake other side as we're done
524 SuspensionChangeOngoing.Set(false);
525 }
526 }
527
528 /**
529 * Called by the engine's (audio) thread once per cycle to check if
530 * streams of voices that were killed due to suspension request have
531 * finally really been deleted by the disk thread.
532 */
533 virtual void ProcessPendingStreamDeletions() {
534 if (!iPendingStreamDeletions) return;
535 //TODO: or shall we better store a list with stream handles instead of a scalar amount of streams to be deleted? might be safer
536 while (
537 iPendingStreamDeletions &&
538 pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE
539 ) iPendingStreamDeletions--;
540 // just for safety ...
541 while (pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE);
542 // now that all disk streams are deleted, awake other side as
543 // we're finally done with suspending the requested region
544 if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false);
545 }
546
547 /**
548 * Returns @c true if the given region is currently set to be suspended
549 * from being used, @c false otherwise.
550 */
551 virtual bool RegionSuspended(RR* pRegion) {
552 if (SuspendedRegions.isEmpty()) return false;
553 //TODO: or shall we use a sorted container instead of the RTList? might be faster ... or trivial ;-)
554 RootRegionIterator iter = SuspendedRegions.first();
555 RootRegionIterator end = SuspendedRegions.end();
556 for (; iter != end; ++iter) // iterate through all suspended regions
557 if (*iter == pRegion) return true;
558 return false;
559 }
560
561 // implementation of abstract method derived from class 'LinuxSampler::RegionPools'
562 virtual Pool<R*>* GetRegionPool(int index) {
563 if (index < 0 || index > 1) throw Exception("Index out of bounds");
564 return pRegionPool[index];
565 }
566
567 // implementation of abstract method derived from class 'LinuxSampler::VoicePool'
568 virtual Pool<V>* GetVoicePool() { return pVoicePool; }
569
570 D* GetDiskThread() { return pDiskThread; }
571
572 //friend class EngineChannelBase<V, R, I>;
573
574 static IM instruments;
575
576 protected:
577 class SuspensionVoiceHandler : public MidiKeyboardManager<V>::VoiceHandler {
578 public:
579 int PendingStreamDeletions;
580 RR* pPendingRegionSuspension;
581
582 SuspensionVoiceHandler(RR* pPendingRegionSuspension) {
583 PendingStreamDeletions = 0;
584 this->pPendingRegionSuspension = pPendingRegionSuspension;
585 }
586
587 virtual bool Process(MidiKey* pMidiKey) OVERRIDE {
588 VoiceIterator itVoice = pMidiKey->pActiveVoices->first();
589 // if current key is not associated with this region, skip this key
590 if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false;
591
592 return true;
593 }
594
595 virtual void Process(VoiceIterator& itVoice) OVERRIDE {
596 // request a notification from disk thread side for stream deletion
597 const Stream::Handle hStream = itVoice->KillImmediately(true);
598 if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream
599 PendingStreamDeletions++;
600 }
601 //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
602 }
603 };
604
605 Pool<R*>* pRegionPool[2]; ///< Double buffered pool, used by the engine channels to keep track of regions in use.
606 int MinFadeOutSamples; ///< The number of samples needed to make an instant fade out (e.g. for voice stealing) without leading to clicks.
607 D* pDiskThread;
608
609 int ActiveVoiceCountTemp; ///< number of currently active voices (for internal usage, will be used for incrementation)
610 VoiceIterator itLastStolenVoice; ///< Only for voice stealing: points to the last voice which was theft in current audio fragment, NULL otherwise.
611 RTList<uint>::Iterator iuiLastStolenKey; ///< Only for voice stealing: key number of last key on which the last voice was theft in current audio fragment, NULL otherwise.
612 EngineChannelBase<V, R, I>* pLastStolenChannel; ///< Only for voice stealing: points to the engine channel on which the previous voice was stolen in this audio fragment.
613 VoiceIterator itLastStolenVoiceGlobally; ///< Same as itLastStolenVoice, but engine globally
614 RTList<uint>::Iterator iuiLastStolenKeyGlobally; ///< Same as iuiLastStolenKey, but engine globally
615 RTList<Event>* pVoiceStealingQueue; ///< All voice-launching events which had to be postponed due to free voice shortage.
616 Mutex ResetInternalMutex; ///< Mutex to protect the ResetInternal function for concurrent usage (e.g. by the lscp and instrument loader threads).
617 int iMaxDiskStreams;
618
619 /**
620 * Dispatch and handle all events in this audio fragment for the given
621 * engine channel.
622 *
623 * @param pEngineChannel - engine channel on which events should be
624 * processed
625 * @param Samples - amount of sample points to be processed in
626 * this audio fragment cycle
627 */
628 void ProcessEvents(EngineChannel* pEngineChannel, uint Samples) {
629 // get all events from the engine channels's input event queue which belong to the current fragment
630 // (these are the common events like NoteOn, NoteOff, ControlChange, etc.)
631 AbstractEngineChannel* pChannel = static_cast<AbstractEngineChannel*>(pEngineChannel);
632 pChannel->ImportEvents(Samples);
633
634 // if a valid real-time instrument script is loaded, pre-process
635 // the event list by running the script now, since the script
636 // might filter events or add new ones for this cycle
637 if (pChannel->script.bHasValidScript) {
638 // resume any suspended script executions still hanging
639 // around of previous audio fragment cycles
640 for (RTList<ScriptEvent>::Iterator itEvent = pChannel->pScriptEvents->first(),
641 end = pChannel->pScriptEvents->end(); itEvent != end; ++itEvent)
642 {
643 ResumeScriptEvent(pChannel, itEvent); //TODO: implement support for actual suspension time (i.e. passed to a script's wait() function call)
644 }
645
646 // spawn new script executions for the new MIDI events of
647 // this audio fragment cycle
648 for (RTList<Event>::Iterator itEvent = pChannel->pEvents->first(),
649 end = pChannel->pEvents->end(); itEvent != end; ++itEvent)
650 {
651 switch (itEvent->Type) {
652 case Event::type_note_on:
653 if (pChannel->script.handlerNote)
654 ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerNote);
655 break;
656 case Event::type_note_off:
657 if (pChannel->script.handlerRelease)
658 ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerRelease);
659 break;
660 case Event::type_control_change:
661 case Event::type_channel_pressure:
662 case Event::type_pitchbend:
663 if (pChannel->script.handlerController)
664 ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerController);
665 break;
666 case Event::type_note_pressure:
667 //TODO: ...
668 break;
669 }
670 }
671 }
672
673 // now process all events regularly
674 {
675 RTList<Event>::Iterator itEvent = pChannel->pEvents->first();
676 RTList<Event>::Iterator end = pChannel->pEvents->end();
677 for (; itEvent != end; ++itEvent) {
678 switch (itEvent->Type) {
679 case Event::type_note_on:
680 dmsg(5,("Engine: Note on received\n"));
681 ProcessNoteOn((EngineChannel*)itEvent->pEngineChannel, itEvent);
682 break;
683 case Event::type_note_off:
684 dmsg(5,("Engine: Note off received\n"));
685 ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent);
686 break;
687 case Event::type_control_change:
688 dmsg(5,("Engine: MIDI CC received\n"));
689 ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent);
690 break;
691 case Event::type_channel_pressure:
692 dmsg(5,("Engine: MIDI Chan. Pressure received\n"));
693 ProcessChannelPressure((EngineChannel*)itEvent->pEngineChannel, itEvent);
694 break;
695 case Event::type_note_pressure:
696 dmsg(5,("Engine: MIDI Note Pressure received\n"));
697 ProcessPolyphonicKeyPressure((EngineChannel*)itEvent->pEngineChannel, itEvent);
698 break;
699 case Event::type_pitchbend:
700 dmsg(5,("Engine: Pitchbend received\n"));
701 ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent);
702 break;
703 }
704 }
705 }
706
707 // reset voice stealing for the next engine channel (or next audio fragment)
708 itLastStolenVoice = VoiceIterator();
709 itLastStolenVoiceGlobally = VoiceIterator();
710 iuiLastStolenKey = RTList<uint>::Iterator();
711 iuiLastStolenKeyGlobally = RTList<uint>::Iterator();
712 pLastStolenChannel = NULL;
713 }
714
715 /** @brief Call instrument script's event handler for this event.
716 *
717 * Causes a new execution instance of the currently loaded real-time
718 * instrument script's event handler (callback) to be spawned for
719 * the given MIDI event.
720 *
721 * @param pChannel - engine channel on which the MIDI event occured
722 * @param itEvent - MIDI event that causes this new script execution
723 * @param pEventHandler - script's event handler to be executed
724 */
725 void ProcessEventByScript(AbstractEngineChannel* pChannel, RTList<Event>::Iterator& itEvent, VMEventHandler* pEventHandler) {
726 RTList<ScriptEvent>::Iterator itScriptEvent =
727 pChannel->pScriptEvents->allocAppend();
728
729 if (!itScriptEvent) return; // no free script event left for execution
730
731 // fill the list of script handlers to be executed by this event
732 int i = 0;
733 if (pChannel->script.handlerInit)
734 itScriptEvent->handlers[i++] = pChannel->script.handlerInit;
735 itScriptEvent->handlers[i++] = pEventHandler; // actual event handler (i.e. note, controller)
736 itScriptEvent->handlers[i] = NULL; // NULL termination of list
737
738 // initialize/reset other members
739 itScriptEvent->cause = *itEvent;
740 itScriptEvent->id = pEventPool->getID(itEvent);
741 itScriptEvent->currentHandler = 0;
742 itScriptEvent->executionSlices = 0;
743
744 // run script handler(s)
745 VMExecStatus_t res = pScriptVM->exec(
746 pChannel->script.parserContext, &*itScriptEvent
747 );
748
749 // in case the script was suspended, keep it on the allocated
750 // ScriptEvent list to be continued on the next audio cycle,
751 // otherwise if execution has been finished, free it for a new
752 // future script event to be triggered from start
753 if (!(res & VM_EXEC_SUSPENDED))
754 pChannel->pScriptEvents->free(itScriptEvent);
755 }
756
757 /** @brief Resume execution of instrument script.
758 *
759 * Will be called to resume execution of a real-time instrument
760 * script event which has been suspended in a previous audio
761 * fragment cycle.
762 *
763 * Script execution might be suspended for various reasons. Usually
764 * a script will be suspended if the script called the built-in
765 * "wait()" function, but it might also be suspended automatically
766 * if the script took too much execution time in an audio fragment
767 * cycle. So in the latter case automatic suspension is performed in
768 * order to avoid harm for the sampler's overall real-time
769 * requirements.
770 *
771 * @param pChannel - engine channel this script is running for
772 * @param itScriptEvent - script execution that shall be resumed
773 */
774 void ResumeScriptEvent(AbstractEngineChannel* pChannel, RTList<ScriptEvent>::Iterator& itScriptEvent) {
775 // run script
776 VMExecStatus_t res = pScriptVM->exec(
777 pChannel->script.parserContext, &*itScriptEvent
778 );
779 // in case the script was again suspended, keep it on the allocated
780 // ScriptEvent list to be continued on the next audio cycle,
781 // otherwise if execution has been finished, free it for a new
782 // future script event to be triggered from start
783 if (!(res & VM_EXEC_SUSPENDED))
784 pChannel->pScriptEvents->free(itScriptEvent);
785 }
786
787 /**
788 * Will be called by LaunchVoice() method in case there are no free
789 * voices left. This method will select and kill one old voice for
790 * voice stealing and postpone the note-on event until the selected
791 * voice actually died.
792 *
793 * @param pEngineChannel - engine channel on which this event occured on
794 * @param itNoteOnEvent - key, velocity and time stamp of the event
795 * @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing
796 */
797 int StealVoice(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) {
798 if (VoiceSpawnsLeft <= 0) {
799 dmsg(1,("Max. voice thefts per audio fragment reached (you may raise CONFIG_MAX_VOICES).\n"));
800 return -1;
801 }
802
803 EngineChannelBase<V, R, I>* pEngineChn = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
804
805 if (!pEventPool->poolIsEmpty()) {
806
807 if(!pEngineChn->StealVoice(itNoteOnEvent, &itLastStolenVoice, &iuiLastStolenKey)) {
808 --VoiceSpawnsLeft;
809 return 0;
810 }
811
812 // if we couldn't steal a voice from the same engine channel then
813 // steal oldest voice on the oldest key from any other engine channel
814 // (the smaller engine channel number, the higher priority)
815 EngineChannelBase<V, R, I>* pSelectedChannel;
816 int iChannelIndex;
817 VoiceIterator itSelectedVoice;
818
819 // select engine channel
820 if (pLastStolenChannel) {
821 pSelectedChannel = pLastStolenChannel;
822 iChannelIndex = pSelectedChannel->iEngineIndexSelf;
823 } else { // pick the engine channel followed by this engine channel
824 iChannelIndex = (pEngineChn->iEngineIndexSelf + 1) % engineChannels.size();
825 pSelectedChannel = static_cast<EngineChannelBase<V, R, I>*>(engineChannels[iChannelIndex]);
826 }
827
828 // if we already stole in this fragment, try to proceed on same key
829 if (this->itLastStolenVoiceGlobally) {
830 itSelectedVoice = this->itLastStolenVoiceGlobally;
831 do {
832 ++itSelectedVoice;
833 } while (itSelectedVoice && !itSelectedVoice->IsStealable()); // proceed iterating if voice was created in this fragment cycle
834 }
835
836 #if CONFIG_DEVMODE
837 EngineChannel* pBegin = pSelectedChannel; // to detect endless loop
838 #endif // CONFIG_DEVMODE
839
840 // did we find a 'stealable' voice?
841 if (itSelectedVoice && itSelectedVoice->IsStealable()) {
842 // remember which voice we stole, so we can simply proceed on next voice stealing
843 this->itLastStolenVoiceGlobally = itSelectedVoice;
844 } else while (true) { // iterate through engine channels
845 // get (next) oldest key
846 RTList<uint>::Iterator iuiSelectedKey = (this->iuiLastStolenKeyGlobally) ? ++this->iuiLastStolenKeyGlobally : pSelectedChannel->pActiveKeys->first();
847 this->iuiLastStolenKeyGlobally = RTList<uint>::Iterator(); // to prevent endless loop (see line above)
848 while (iuiSelectedKey) {
849 MidiKey* pSelectedKey = &pSelectedChannel->pMIDIKeyInfo[*iuiSelectedKey];
850 itSelectedVoice = pSelectedKey->pActiveVoices->first();
851 // proceed iterating if voice was created in this fragment cycle
852 while (itSelectedVoice && !itSelectedVoice->IsStealable()) ++itSelectedVoice;
853 // found a "stealable" voice ?
854 if (itSelectedVoice && itSelectedVoice->IsStealable()) {
855 // remember which voice on which key on which engine channel we stole, so we can simply proceed on next voice stealing
856 this->iuiLastStolenKeyGlobally = iuiSelectedKey;
857 this->itLastStolenVoiceGlobally = itSelectedVoice;
858 this->pLastStolenChannel = pSelectedChannel;
859 goto stealable_voice_found; // selection succeeded
860 }
861 ++iuiSelectedKey; // get next key on current engine channel
862 }
863 // get next engine channel
864 iChannelIndex = (iChannelIndex + 1) % engineChannels.size();
865 pSelectedChannel = static_cast<EngineChannelBase<V, R, I>*>(engineChannels[iChannelIndex]);
866
867 #if CONFIG_DEVMODE
868 if (pSelectedChannel == pBegin) {
869 dmsg(1,("FATAL ERROR: voice stealing endless loop!\n"));
870 dmsg(1,("VoiceSpawnsLeft=%d.\n", VoiceSpawnsLeft));
871 dmsg(1,("Exiting.\n"));
872 exit(-1);
873 }
874 #endif // CONFIG_DEVMODE
875 }
876
877 // jump point if a 'stealable' voice was found
878 stealable_voice_found:
879
880 #if CONFIG_DEVMODE
881 if (!itSelectedVoice->IsActive()) {
882 dmsg(1,("EngineBase: ERROR, tried to steal a voice which was not active !!!\n"));
883 return -1;
884 }
885 #endif // CONFIG_DEVMODE
886
887 // now kill the selected voice
888 itSelectedVoice->Kill(itNoteOnEvent);
889
890 --VoiceSpawnsLeft;
891
892 return 0; // success
893 }
894 else {
895 dmsg(1,("Event pool emtpy!\n"));
896 return -1;
897 }
898 }
899
900 void HandleInstrumentChanges() {
901 bool instrumentChanged = false;
902 for (int i = 0; i < engineChannels.size(); i++) {
903 EngineChannelBase<V, R, I>* pEngineChannel =
904 static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]);
905
906 // as we're going to (carefully) write some status to the
907 // synchronized struct, we cast away the const
908 InstrumentChangeCmd<R, I>& cmd =
909 const_cast<InstrumentChangeCmd<R, I>&>(pEngineChannel->InstrumentChangeCommandReader.Lock());
910
911 pEngineChannel->pRegionsInUse = cmd.pRegionsInUse;
912 pEngineChannel->pRegionsInUse->clear();
913
914 if (cmd.bChangeInstrument) {
915 // change instrument
916 dmsg(5,("Engine: instrument change command received\n"));
917 cmd.bChangeInstrument = false;
918 pEngineChannel->pInstrument = cmd.pInstrument;
919 instrumentChanged = true;
920
921 pEngineChannel->MarkAllActiveVoicesAsOrphans();
922 }
923 }
924
925 if (instrumentChanged) {
926 //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
927 ResetSuspendedRegions();
928 }
929 }
930
931 /**
932 * Render all 'normal' voices (that is voices which were not stolen in
933 * this fragment) on the given engine channel.
934 *
935 * @param pEngineChannel - engine channel on which audio should be
936 * rendered
937 * @param Samples - amount of sample points to be rendered in
938 * this audio fragment cycle
939 */
940 void RenderActiveVoices(EngineChannel* pEngineChannel, uint Samples) {
941 #if !CONFIG_PROCESS_MUTED_CHANNELS
942 if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted
943 #endif
944
945 EngineChannelBase<V, R, I>* pChannel =
946 static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
947 pChannel->RenderActiveVoices(Samples);
948
949 ActiveVoiceCountTemp += pEngineChannel->GetVoiceCount();
950 }
951
952 /**
953 * Render all stolen voices (only voices which were stolen in this
954 * fragment) on the given engine channel. Stolen voices are rendered
955 * after all normal voices have been rendered; this is needed to render
956 * audio of those voices which were selected for voice stealing until
957 * the point were the stealing (that is the take over of the voice)
958 * actually happened.
959 *
960 * @param pEngineChannel - engine channel on which audio should be
961 * rendered
962 * @param Samples - amount of sample points to be rendered in
963 * this audio fragment cycle
964 */
965 void RenderStolenVoices(uint Samples) {
966 RTList<Event>::Iterator itVoiceStealEvent = pVoiceStealingQueue->first();
967 RTList<Event>::Iterator end = pVoiceStealingQueue->end();
968 for (; itVoiceStealEvent != end; ++itVoiceStealEvent) {
969 EngineChannelBase<V, R, I>* pEngineChannel =
970 static_cast<EngineChannelBase<V, R, I>*>(itVoiceStealEvent->pEngineChannel);;
971 if (!pEngineChannel->pInstrument) continue; // ignore if no instrument loaded
972 PoolVoiceIterator itNewVoice =
973 LaunchVoice(pEngineChannel, itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false, false);
974 if (itNewVoice) {
975 itNewVoice->Render(Samples);
976 if (itNewVoice->IsActive()) { // still active
977 *(pEngineChannel->pRegionsInUse->allocAppend()) = itNewVoice->GetRegion();
978 ActiveVoiceCountTemp++;
979 pEngineChannel->SetVoiceCount(pEngineChannel->GetVoiceCount() + 1);
980
981 if (itNewVoice->PlaybackState == Voice::playback_state_disk) {
982 if (itNewVoice->DiskStreamRef.State != Stream::state_unused) {
983 pEngineChannel->SetDiskStreamCount(pEngineChannel->GetDiskStreamCount() + 1);
984 }
985 }
986 } else { // voice reached end, is now inactive
987 pEngineChannel->FreeVoice(itNewVoice); // remove voice from the list of active voices
988 }
989 }
990 else dmsg(1,("EngineBase: ERROR, voice stealing didn't work out!\n"));
991
992 // we need to clear the key's event list explicitly here in case key was never active
993 MidiKey* pKey = &pEngineChannel->pMIDIKeyInfo[itVoiceStealEvent->Param.Note.Key];
994 pKey->VoiceTheftsQueued--;
995 if (!pKey->Active && !pKey->VoiceTheftsQueued) pKey->pEvents->clear();
996 }
997 }
998
999 /**
1000 * Free all keys which have turned inactive in this audio fragment, from
1001 * the list of active keys and clear all event lists on that engine
1002 * channel.
1003 *
1004 * @param pEngineChannel - engine channel to cleanup
1005 */
1006 void PostProcess(EngineChannel* pEngineChannel) {
1007 EngineChannelBase<V, R, I>* pChannel =
1008 static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1009 pChannel->FreeAllInactiveKyes();
1010
1011 // empty the engine channel's own event lists
1012 pChannel->ClearEventLists();
1013 }
1014
1015 /**
1016 * Process MIDI control change events with hard coded behavior,
1017 * that is controllers whose behavior is defined independently
1018 * of the actual sampler engine type and instrument.
1019 *
1020 * @param pEngineChannel - engine channel on which the MIDI CC event was received
1021 * @param itControlChangeEvent - the actual MIDI CC event
1022 */
1023 void ProcessHardcodedControllers (
1024 EngineChannel* pEngineChannel,
1025 Pool<Event>::Iterator& itControlChangeEvent
1026 ) {
1027 EngineChannelBase<V, R, I>* pChannel =
1028 static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1029
1030 switch (itControlChangeEvent->Param.CC.Controller) {
1031 case 5: { // portamento time
1032 pChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN;
1033 break;
1034 }
1035 case 6: { // data entry (currently only used for RPN and NRPN controllers)
1036 //dmsg(1,("DATA ENTRY %d\n", itControlChangeEvent->Param.CC.Value));
1037 if (pChannel->GetMidiRpnController() >= 0) { // RPN controller number was sent previously ...
1038 dmsg(4,("Guess it's an RPN ...\n"));
1039 if (pChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones
1040 int transpose = (int) itControlChangeEvent->Param.CC.Value - 64;
1041 // limit to +- two octaves for now
1042 transpose = RTMath::Min(transpose, 24);
1043 transpose = RTMath::Max(transpose, -24);
1044 pChannel->GlobalTranspose = transpose;
1045 // workaround, so we won't have hanging notes
1046 pChannel->ReleaseAllVoices(itControlChangeEvent);
1047 }
1048 // to prevent other MIDI CC #6 messages to be misenterpreted as RPN controller data
1049 pChannel->ResetMidiRpnController();
1050 } else if (pChannel->GetMidiNrpnController() >= 0) { // NRPN controller number was sent previously ...
1051 dmsg(4,("Guess it's an NRPN ...\n"));
1052 const int NrpnCtrlMSB = pChannel->GetMidiNrpnController() >> 8;
1053 const int NrpnCtrlLSB = pChannel->GetMidiNrpnController() & 0xff;
1054 dmsg(4,("NRPN MSB=%d LSB=%d Data=%d\n", NrpnCtrlMSB, NrpnCtrlLSB, itControlChangeEvent->Param.CC.Value));
1055 switch (NrpnCtrlMSB) {
1056 case 0x1a: { // volume level of note (Roland GS NRPN)
1057 const uint note = NrpnCtrlLSB;
1058 const uint vol = itControlChangeEvent->Param.CC.Value;
1059 dmsg(4,("Note Volume NRPN received (note=%d,vol=%d).\n", note, vol));
1060 if (note < 128 && vol < 128)
1061 pChannel->pMIDIKeyInfo[note].Volume = VolumeCurve[vol];
1062 break;
1063 }
1064 case 0x1c: { // panpot of note (Roland GS NRPN)
1065 const uint note = NrpnCtrlLSB;
1066 const uint pan = itControlChangeEvent->Param.CC.Value;
1067 dmsg(4,("Note Pan NRPN received (note=%d,pan=%d).\n", note, pan));
1068 if (note < 128 && pan < 128) {
1069 pChannel->pMIDIKeyInfo[note].PanLeft = PanCurve[128 - pan];
1070 pChannel->pMIDIKeyInfo[note].PanRight = PanCurve[pan];
1071 }
1072 break;
1073 }
1074 case 0x1d: { // reverb send of note (Roland GS NRPN)
1075 const uint note = NrpnCtrlLSB;
1076 const float reverb = float(itControlChangeEvent->Param.CC.Value) / 127.0f;
1077 dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%d).\n", note, reverb));
1078 if (note < 128)
1079 pChannel->pMIDIKeyInfo[note].ReverbSend = reverb;
1080 break;
1081 }
1082 case 0x1e: { // chorus send of note (Roland GS NRPN)
1083 const uint note = NrpnCtrlLSB;
1084 const float chorus = float(itControlChangeEvent->Param.CC.Value) / 127.0f;
1085 dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%d).\n", note, chorus));
1086 if (note < 128)
1087 pChannel->pMIDIKeyInfo[note].ChorusSend = chorus;
1088 break;
1089 }
1090 }
1091 // to prevent other MIDI CC #6 messages to be misenterpreted as NRPN controller data
1092 pChannel->ResetMidiNrpnController();
1093 }
1094 break;
1095 }
1096 case 7: { // volume
1097 //TODO: not sample accurate yet
1098 pChannel->MidiVolume = VolumeCurve[itControlChangeEvent->Param.CC.Value];
1099 pChannel->bStatusChanged = true; // engine channel status has changed, so set notify flag
1100 break;
1101 }
1102 case 10: { // panpot
1103 //TODO: not sample accurate yet
1104 pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value;
1105 break;
1106 }
1107 case 64: { // sustain
1108 if (itControlChangeEvent->Param.CC.Value >= 64 && !pChannel->SustainPedal) {
1109 dmsg(4,("DAMPER (RIGHT) PEDAL DOWN\n"));
1110 pChannel->SustainPedal = true;
1111 pChannel->listeners.PreProcessSustainPedalDown();
1112
1113 #if !CONFIG_PROCESS_MUTED_CHANNELS
1114 if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1115 pChannel->listeners.PostProcessSustainPedalDown();
1116 return;
1117 }
1118 #endif
1119
1120 pChannel->ProcessSustainPedalDown(itControlChangeEvent);
1121 pChannel->listeners.PostProcessSustainPedalDown();
1122 }
1123 if (itControlChangeEvent->Param.CC.Value < 64 && pChannel->SustainPedal) {
1124 dmsg(4,("DAMPER (RIGHT) PEDAL UP\n"));
1125 pChannel->SustainPedal = false;
1126 pChannel->listeners.PreProcessSustainPedalUp();
1127
1128 #if !CONFIG_PROCESS_MUTED_CHANNELS
1129 if (pChannel->GetMute()) { // skip if sampler channel is muted
1130 pChannel->listeners.PostProcessSustainPedalUp();
1131 return;
1132 }
1133 #endif
1134
1135 pChannel->ProcessSustainPedalUp(itControlChangeEvent);
1136 pChannel->listeners.PostProcessSustainPedalUp();
1137 }
1138 break;
1139 }
1140 case 65: { // portamento on / off
1141 const bool bPortamento = itControlChangeEvent->Param.CC.Value >= 64;
1142 if (bPortamento != pChannel->PortamentoMode)
1143 KillAllVoices(pChannel, itControlChangeEvent);
1144 pChannel->PortamentoMode = bPortamento;
1145 break;
1146 }
1147 case 66: { // sostenuto
1148 if (itControlChangeEvent->Param.CC.Value >= 64 && !pChannel->SostenutoPedal) {
1149 dmsg(4,("SOSTENUTO (CENTER) PEDAL DOWN\n"));
1150 pChannel->SostenutoPedal = true;
1151 pChannel->listeners.PreProcessSostenutoPedalDown();
1152
1153 #if !CONFIG_PROCESS_MUTED_CHANNELS
1154 if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1155 pChannel->listeners.PostProcessSostenutoPedalDown();
1156 return;
1157 }
1158 #endif
1159
1160 pChannel->ProcessSostenutoPedalDown();
1161 pChannel->listeners.PostProcessSostenutoPedalDown();
1162 }
1163 if (itControlChangeEvent->Param.CC.Value < 64 && pChannel->SostenutoPedal) {
1164 dmsg(4,("SOSTENUTO (CENTER) PEDAL UP\n"));
1165 pChannel->SostenutoPedal = false;
1166 pChannel->listeners.PreProcessSostenutoPedalUp();
1167
1168 #if !CONFIG_PROCESS_MUTED_CHANNELS
1169 if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1170 pChannel->listeners.PostProcessSostenutoPedalUp();
1171 return;
1172 }
1173 #endif
1174
1175 pChannel->ProcessSostenutoPedalUp(itControlChangeEvent);
1176 pChannel->listeners.PostProcessSostenutoPedalUp();
1177 }
1178 break;
1179 }
1180 case 98: { // NRPN controller LSB
1181 dmsg(4,("NRPN LSB %d\n", itControlChangeEvent->Param.CC.Value));
1182 pEngineChannel->SetMidiNrpnControllerLsb(itControlChangeEvent->Param.CC.Value);
1183 break;
1184 }
1185 case 99: { // NRPN controller MSB
1186 dmsg(4,("NRPN MSB %d\n", itControlChangeEvent->Param.CC.Value));
1187 pEngineChannel->SetMidiNrpnControllerMsb(itControlChangeEvent->Param.CC.Value);
1188 break;
1189 }
1190 case 100: { // RPN controller LSB
1191 dmsg(4,("RPN LSB %d\n", itControlChangeEvent->Param.CC.Value));
1192 pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value);
1193 break;
1194 }
1195 case 101: { // RPN controller MSB
1196 dmsg(4,("RPN MSB %d\n", itControlChangeEvent->Param.CC.Value));
1197 pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value);
1198 break;
1199 }
1200
1201
1202 // Channel Mode Messages
1203
1204 case 120: { // all sound off
1205 KillAllVoices(pEngineChannel, itControlChangeEvent);
1206 break;
1207 }
1208 case 121: { // reset all controllers
1209 pChannel->ResetControllers();
1210 break;
1211 }
1212 case 123: { // all notes off
1213 #if CONFIG_PROCESS_ALL_NOTES_OFF
1214 pChannel->ReleaseAllVoices(itControlChangeEvent);
1215 #endif // CONFIG_PROCESS_ALL_NOTES_OFF
1216 break;
1217 }
1218 case 126: { // mono mode on
1219 if (!pChannel->SoloMode)
1220 KillAllVoices(pEngineChannel, itControlChangeEvent);
1221 pChannel->SoloMode = true;
1222 break;
1223 }
1224 case 127: { // poly mode on
1225 if (pChannel->SoloMode)
1226 KillAllVoices(pEngineChannel, itControlChangeEvent);
1227 pChannel->SoloMode = false;
1228 break;
1229 }
1230 }
1231 }
1232
1233 virtual D* CreateDiskThread() = 0;
1234
1235 /**
1236 * Assigns and triggers a new voice for the respective MIDI key.
1237 *
1238 * @param pEngineChannel - engine channel on which this event occured on
1239 * @param itNoteOnEvent - key, velocity and time stamp of the event
1240 */
1241 virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) {
1242 EngineChannelBase<V, R, I>* pChannel =
1243 static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1244
1245 //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
1246 int k = itNoteOnEvent->Param.Note.Key + pChannel->GlobalTranspose;
1247 if (k < 0 || k > 127) return; //ignore keys outside the key range
1248
1249 itNoteOnEvent->Param.Note.Key += pChannel->GlobalTranspose;
1250 int vel = itNoteOnEvent->Param.Note.Velocity;
1251
1252 const int key = itNoteOnEvent->Param.Note.Key;
1253 MidiKey* pKey = &pChannel->pMIDIKeyInfo[key];
1254
1255 pChannel->listeners.PreProcessNoteOn(key, vel);
1256 #if !CONFIG_PROCESS_MUTED_CHANNELS
1257 if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1258 pChannel->listeners.PostProcessNoteOn(key, vel);
1259 return;
1260 }
1261 #endif
1262
1263 if (!pChannel->pInstrument) {
1264 pChannel->listeners.PostProcessNoteOn(key, vel);
1265 return; // ignore if no instrument loaded
1266 }
1267
1268 // move note on event to the key's own event list
1269 RTList<Event>::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents);
1270
1271 // if Solo Mode then kill all already active voices
1272 if (pChannel->SoloMode) {
1273 Pool<uint>::Iterator itYoungestKey = pChannel->pActiveKeys->last();
1274 if (itYoungestKey) {
1275 const int iYoungestKey = *itYoungestKey;
1276 const MidiKey* pOtherKey = &pChannel->pMIDIKeyInfo[iYoungestKey];
1277 if (pOtherKey->Active) {
1278 // get final portamento position of currently active voice
1279 if (pChannel->PortamentoMode) {
1280 VoiceIterator itVoice = pOtherKey->pActiveVoices->last();
1281 if (itVoice) itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList);
1282 }
1283 // kill all voices on the (other) key
1284 VoiceIterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first();
1285 VoiceIterator end = pOtherKey->pActiveVoices->end();
1286 for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) {
1287 if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger))
1288 itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList);
1289 }
1290 }
1291 }
1292 // set this key as 'currently active solo key'
1293 pChannel->SoloKey = key;
1294 }
1295
1296 pChannel->ProcessKeySwitchChange(key);
1297
1298 pKey->KeyPressed = true; // the MIDI key was now pressed down
1299 pKey->Velocity = itNoteOnEventOnKeyList->Param.Note.Velocity;
1300 pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length
1301
1302 // cancel release process of voices on this key if needed
1303 if (pKey->Active && !pChannel->SustainPedal) {
1304 RTList<Event>::Iterator itCancelReleaseEvent = pKey->pEvents->allocAppend();
1305 if (itCancelReleaseEvent) {
1306 *itCancelReleaseEvent = *itNoteOnEventOnKeyList; // copy event
1307 itCancelReleaseEvent->Type = Event::type_cancel_release; // transform event type
1308 }
1309 else dmsg(1,("Event pool emtpy!\n"));
1310 }
1311
1312 TriggerNewVoices(pEngineChannel, itNoteOnEventOnKeyList);
1313
1314 // if neither a voice was spawned or postponed then remove note on event from key again
1315 if (!pKey->Active && !pKey->VoiceTheftsQueued)
1316 pKey->pEvents->free(itNoteOnEventOnKeyList);
1317
1318 if (!pChannel->SoloMode || pChannel->PortamentoPos < 0.0f) pChannel->PortamentoPos = (float) key;
1319 if (pKey->pRoundRobinIndex) {
1320 (*pKey->pRoundRobinIndex)++; // counter specific for the key or region
1321 pChannel->RoundRobinIndex++; // common counter for the channel
1322 }
1323 pChannel->listeners.PostProcessNoteOn(key, vel);
1324 }
1325
1326 /**
1327 * Allocate and trigger new voice(s) for the key.
1328 */
1329 virtual void TriggerNewVoices (
1330 EngineChannel* pEngineChannel,
1331 RTList<Event>::Iterator& itNoteOnEvent,
1332 bool HandleKeyGroupConflicts = true
1333 ) = 0;
1334
1335 /**
1336 * Allocate and trigger release voice(s) for the key.
1337 */
1338 virtual void TriggerReleaseVoices (
1339 EngineChannel* pEngineChannel,
1340 RTList<Event>::Iterator& itNoteOffEvent
1341 ) = 0;
1342
1343 /**
1344 * Releases the voices on the given key if sustain pedal is not pressed.
1345 * If sustain is pressed, the release of the note will be postponed until
1346 * sustain pedal will be released or voice turned inactive by itself (e.g.
1347 * due to completion of sample playback).
1348 *
1349 * @param pEngineChannel - engine channel on which this event occured on
1350 * @param itNoteOffEvent - key, velocity and time stamp of the event
1351 */
1352 virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOffEvent) {
1353 EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1354
1355 int k = itNoteOffEvent->Param.Note.Key + pChannel->GlobalTranspose;
1356 if (k < 0 || k > 127) return; //ignore keys outside the key range
1357
1358 //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
1359 itNoteOffEvent->Param.Note.Key += pChannel->GlobalTranspose;
1360 int vel = itNoteOffEvent->Param.Note.Velocity;
1361
1362 const int iKey = itNoteOffEvent->Param.Note.Key;
1363 MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey];
1364
1365 pChannel->listeners.PreProcessNoteOff(iKey, vel);
1366
1367 #if !CONFIG_PROCESS_MUTED_CHANNELS
1368 if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1369 pChannel->listeners.PostProcessNoteOff(iKey, vel);
1370 return;
1371 }
1372 #endif
1373
1374 pKey->KeyPressed = false; // the MIDI key was now released
1375
1376 // move event to the key's own event list
1377 RTList<Event>::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents);
1378
1379 bool bShouldRelease = pKey->Active && pChannel->ShouldReleaseVoice(itNoteOffEventOnKeyList->Param.Note.Key);
1380
1381 // in case Solo Mode is enabled, kill all voices on this key and respawn a voice on the highest pressed key (if any)
1382 if (pChannel->SoloMode && pChannel->pInstrument) { //TODO: this feels like too much code just for handling solo mode :P
1383 bool bOtherKeysPressed = false;
1384 if (iKey == pChannel->SoloKey) {
1385 pChannel->SoloKey = -1;
1386 // if there's still a key pressed down, respawn a voice (group) on the highest key
1387 for (int i = 127; i > 0; i--) {
1388 MidiKey* pOtherKey = &pChannel->pMIDIKeyInfo[i];
1389 if (pOtherKey->KeyPressed) {
1390 bOtherKeysPressed = true;
1391 // make the other key the new 'currently active solo key'
1392 pChannel->SoloKey = i;
1393 // get final portamento position of currently active voice
1394 if (pChannel->PortamentoMode) {
1395 VoiceIterator itVoice = pKey->pActiveVoices->first();
1396 if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList);
1397 }
1398 // create a pseudo note on event
1399 RTList<Event>::Iterator itPseudoNoteOnEvent = pOtherKey->pEvents->allocAppend();
1400 if (itPseudoNoteOnEvent) {
1401 // copy event
1402 *itPseudoNoteOnEvent = *itNoteOffEventOnKeyList;
1403 // transform event to a note on event
1404 itPseudoNoteOnEvent->Type = Event::type_note_on;
1405 itPseudoNoteOnEvent->Param.Note.Key = i;
1406 itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity;
1407 // allocate and trigger new voice(s) for the other key
1408 TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false);
1409 // if neither a voice was spawned or postponed then remove note on event from key again
1410 if (!pOtherKey->Active && !pOtherKey->VoiceTheftsQueued)
1411 pOtherKey->pEvents->free(itPseudoNoteOnEvent);
1412
1413 } else dmsg(1,("Could not respawn voice, no free event left\n"));
1414 break; // done
1415 }
1416 }
1417 }
1418 if (bOtherKeysPressed) {
1419 if (pKey->Active) { // kill all voices on this key
1420 bShouldRelease = false; // no need to release, as we kill it here
1421 VoiceIterator itVoiceToBeKilled = pKey->pActiveVoices->first();
1422 VoiceIterator end = pKey->pActiveVoices->end();
1423 for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) {
1424 if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger))
1425 itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList);
1426 }
1427 }
1428 } else pChannel->PortamentoPos = -1.0f;
1429 }
1430
1431 // if no solo mode (the usual case) or if solo mode and no other key pressed, then release voices on this key if needed
1432 if (bShouldRelease) {
1433 itNoteOffEventOnKeyList->Type = Event::type_release; // transform event type
1434
1435 // spawn release triggered voice(s) if needed
1436 if (pKey->ReleaseTrigger && pChannel->pInstrument) {
1437 TriggerReleaseVoices(pChannel, itNoteOffEventOnKeyList);
1438 pKey->ReleaseTrigger = false;
1439 }
1440 }
1441
1442 // if neither a voice was spawned or postponed on this key then remove note off event from key again
1443 if (!pKey->Active && !pKey->VoiceTheftsQueued)
1444 pKey->pEvents->free(itNoteOffEventOnKeyList);
1445
1446 pChannel->listeners.PostProcessNoteOff(iKey, vel);
1447 }
1448
1449 /**
1450 * Reset all voices and disk thread and clear input event queue and all
1451 * control and status variables. This method is protected by a mutex.
1452 */
1453 virtual void ResetInternal() {
1454 LockGuard lock(ResetInternalMutex);
1455
1456 // make sure that the engine does not get any sysex messages
1457 // while it's reseting
1458 bool sysexDisabled = MidiInputPort::RemoveSysexListener(this);
1459 SetVoiceCount(0);
1460 ActiveVoiceCountMax = 0;
1461
1462 // reset voice stealing parameters
1463 pVoiceStealingQueue->clear();
1464 itLastStolenVoice = VoiceIterator();
1465 itLastStolenVoiceGlobally = VoiceIterator();
1466 iuiLastStolenKey = RTList<uint>::Iterator();
1467 iuiLastStolenKeyGlobally = RTList<uint>::Iterator();
1468 pLastStolenChannel = NULL;
1469
1470 // reset all voices
1471 for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
1472 iterVoice->Reset();
1473 }
1474 pVoicePool->clear();
1475
1476 // reset disk thread
1477 if (pDiskThread) pDiskThread->Reset();
1478
1479 // delete all input events
1480 pEventQueue->init();
1481 pSysexBuffer->init();
1482 if (sysexDisabled) MidiInputPort::AddSysexListener(this);
1483 }
1484
1485 /**
1486 * Kills all voices on an engine channel as soon as possible. Voices
1487 * won't get into release state, their volume level will be ramped down
1488 * as fast as possible.
1489 *
1490 * @param pEngineChannel - engine channel on which all voices should be killed
1491 * @param itKillEvent - event which caused this killing of all voices
1492 */
1493 virtual void KillAllVoices(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itKillEvent) {
1494 EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1495 int count = pChannel->KillAllVoices(itKillEvent);
1496 VoiceSpawnsLeft -= count; //FIXME: just a temporary workaround, we should check the cause in StealVoice() instead
1497 }
1498
1499 /**
1500 * Allocates and triggers a new voice. This method will usually be
1501 * called by the ProcessNoteOn() method and by the voices itself
1502 * (e.g. to spawn further voices on the same key for layered sounds).
1503 *
1504 * @param pEngineChannel - engine channel on which this event occured on
1505 * @param itNoteOnEvent - key, velocity and time stamp of the event
1506 * @param iLayer - layer index for the new voice (optional - only
1507 * in case of layered sounds of course)
1508 * @param ReleaseTriggerVoice - if new voice is a release triggered voice
1509 * (optional, default = false)
1510 * @param VoiceStealing - if voice stealing should be performed
1511 * when there is no free voice
1512 * (optional, default = true)
1513 * @param HandleKeyGroupConflicts - if voices should be killed due to a
1514 * key group conflict
1515 * @returns pointer to new voice or NULL if there was no free voice or
1516 * if the voice wasn't triggered (for example when no region is
1517 * defined for the given key).
1518 */
1519 virtual PoolVoiceIterator LaunchVoice (
1520 EngineChannel* pEngineChannel,
1521 Pool<Event>::Iterator& itNoteOnEvent,
1522 int iLayer,
1523 bool ReleaseTriggerVoice,
1524 bool VoiceStealing,
1525 bool HandleKeyGroupConflicts
1526 ) = 0;
1527
1528 virtual int GetMinFadeOutSamples() { return MinFadeOutSamples; }
1529
1530 int InitNewVoice (
1531 EngineChannelBase<V, R, I>* pChannel,
1532 R* pRegion,
1533 Pool<Event>::Iterator& itNoteOnEvent,
1534 Voice::type_t VoiceType,
1535 int iLayer,
1536 int iKeyGroup,
1537 bool ReleaseTriggerVoice,
1538 bool VoiceStealing,
1539 typename Pool<V>::Iterator& itNewVoice
1540 ) {
1541 int key = itNoteOnEvent->Param.Note.Key;
1542 typename MidiKeyboardManager<V>::MidiKey* pKey = &pChannel->pMIDIKeyInfo[key];
1543 if (itNewVoice) {
1544 // launch the new voice
1545 if (itNewVoice->Trigger(pChannel, itNoteOnEvent, pChannel->Pitch, pRegion, VoiceType, iKeyGroup) < 0) {
1546 dmsg(4,("Voice not triggered\n"));
1547 pKey->pActiveVoices->free(itNewVoice);
1548 }
1549 else { // on success
1550 --VoiceSpawnsLeft;
1551 if (!pKey->Active) { // mark as active key
1552 pKey->Active = true;
1553 pKey->itSelf = pChannel->pActiveKeys->allocAppend();
1554 *pKey->itSelf = itNoteOnEvent->Param.Note.Key;
1555 }
1556 if (itNewVoice->Type & Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s)
1557 return 0; // success
1558 }
1559 }
1560 else if (VoiceStealing) {
1561 // try to steal one voice
1562 int result = StealVoice(pChannel, itNoteOnEvent);
1563 if (!result) { // voice stolen successfully
1564 // put note-on event into voice-stealing queue, so it will be reprocessed after killed voice died
1565 RTList<Event>::Iterator itStealEvent = pVoiceStealingQueue->allocAppend();
1566 if (itStealEvent) {
1567 *itStealEvent = *itNoteOnEvent; // copy event
1568 itStealEvent->Param.Note.Layer = iLayer;
1569 itStealEvent->Param.Note.ReleaseTrigger = ReleaseTriggerVoice;
1570 pKey->VoiceTheftsQueued++;
1571 }
1572 else dmsg(1,("Voice stealing queue full!\n"));
1573 }
1574 }
1575
1576 return -1;
1577 }
1578
1579 /**
1580 * Checks whether scale tuning setting has been changed since last
1581 * time this method was called, if yes, it recalculates the pitch
1582 * for all active voices.
1583 */
1584 void ProcessScaleTuningChange() {
1585 const bool changed = ScaleTuningChanged.readAndReset();
1586 if (!changed) return;
1587
1588 for (int i = 0; i < engineChannels.size(); i++) {
1589 EngineChannelBase<V, R, I>* channel =
1590 static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]);
1591 channel->OnScaleTuningChanged();
1592 }
1593 }
1594
1595 private:
1596 Pool<V>* pVoicePool; ///< Contains all voices that can be activated.
1597 Pool<RR*> SuspendedRegions;
1598 Mutex SuspendedRegionsMutex;
1599 Condition SuspensionChangeOngoing;
1600 RR* pPendingRegionSuspension;
1601 RR* pPendingRegionResumption;
1602 int iPendingStreamDeletions;
1603 };
1604
1605 template <class V, class RR, class R, class D, class IM, class I>
1606 IM EngineBase<V, RR, R, D, IM, I>::instruments;
1607
1608 } // namespace LinuxSampler
1609
1610 #endif /* __LS_ENGINEBASE_H__ */
1611

  ViewVC Help
Powered by ViewVC