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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 2115 by persson, Thu Aug 12 15:36:15 2010 UTC revision 2871 by schoenebeck, Sun Apr 10 18:22:23 2016 UTC
# Line 4  Line 4 
4   *                                                                         *   *                                                                         *
5   *   Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck    *   *   Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck    *
6   *   Copyright (C) 2005-2008 Christian Schoenebeck                         *   *   Copyright (C) 2005-2008 Christian Schoenebeck                         *
7   *   Copyright (C) 2009-2010 Christian Schoenebeck and Grigor Iliev        *   *   Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev        *
8     *   Copyright (C) 2012-2016 Christian Schoenebeck and Andreas Persson     *
9   *                                                                         *   *                                                                         *
10   *   This program is free software; you can redistribute it and/or modify  *   *   This program is free software; you can redistribute it and/or modify  *
11   *   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  *
# Line 104  namespace LinuxSampler { Line 105  namespace LinuxSampler {
105               *  @param Samples - number of sample points to be rendered               *  @param Samples - number of sample points to be rendered
106               *  @returns       0 on success               *  @returns       0 on success
107               */               */
108              virtual int RenderAudio(uint Samples) {              virtual int RenderAudio(uint Samples) OVERRIDE {
109                  dmsg(8,("RenderAudio(Samples=%d)\n", Samples));                  dmsg(8,("RenderAudio(Samples=%d)\n", Samples));
110    
111                  // return if engine disabled                  // return if engine disabled
# Line 143  namespace LinuxSampler { Line 144  namespace LinuxSampler {
144                          }                          }
145                      }                      }
146                  }                  }
147                    
148                    // In case scale tuning has been changed, recalculate pitch for
149                    // all active voices.
150                    ProcessScaleTuningChange();
151    
152                  // reset internal voice counter (just for statistic of active voices)                  // reset internal voice counter (just for statistic of active voices)
153                  ActiveVoiceCountTemp = 0;                  ActiveVoiceCountTemp = 0;
# Line 190  namespace LinuxSampler { Line 195  namespace LinuxSampler {
195                  // been deleted by the disk thread                  // been deleted by the disk thread
196                  if (iPendingStreamDeletions) ProcessPendingStreamDeletions();                  if (iPendingStreamDeletions) ProcessPendingStreamDeletions();
197    
198                    // Release the instrument change command. (This has to
199                    // be done after all voices have been rendered and not
200                    // in HandleInstrumentChanges, as the RegionsInUse
201                    // list has been built up by the voice renderers.)
202                    for (int i = 0; i < engineChannels.size(); i++) {
203                        EngineChannelBase<V, R, I>* channel =
204                            static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]);
205                        channel->InstrumentChangeCommandReader.Unlock();
206                    }
207                  FrameTime += Samples;                  FrameTime += Samples;
208    
209                  EngineDisabled.RttDone();                  EngineDisabled.RttDone();
210                  return 0;                  return 0;
211              }              }
212    
213              virtual int MaxVoices() { return pVoicePool->poolSize(); }              virtual int MaxVoices() OVERRIDE { return pVoicePool->poolSize(); }
214    
215              virtual void SetMaxVoices(int iVoices) throw (Exception) {              virtual void SetMaxVoices(int iVoices) throw (Exception) OVERRIDE {
216                  if (iVoices < 1)                  if (iVoices < 1)
217                      throw Exception("Maximum voices for an engine cannot be set lower than 1");                      throw Exception("Maximum voices for an engine cannot be set lower than 1");
218    
# Line 234  namespace LinuxSampler { Line 248  namespace LinuxSampler {
248                  }                  }
249                  pVoicePool->clear();                  pVoicePool->clear();
250    
251                    PostSetMaxVoices(iVoices);
252                  ResumeAll();                  ResumeAll();
253              }              }
254                
255                /** Called after the new max number of voices is set and before resuming the engine. */
256                virtual void PostSetMaxVoices(int iVoices) { }
257    
258              virtual uint DiskStreamCount() { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; }              virtual uint DiskStreamCount() OVERRIDE { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; }
259              virtual uint DiskStreamCountMax() { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; }              virtual uint DiskStreamCountMax() OVERRIDE { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; }
260              virtual int  MaxDiskStreams() { return iMaxDiskStreams; }              virtual int  MaxDiskStreams() OVERRIDE { return iMaxDiskStreams; }
261    
262              virtual void SetMaxDiskStreams(int iStreams) throw (Exception) {              virtual void SetMaxDiskStreams(int iStreams) throw (Exception) OVERRIDE {
263                  if (iStreams < 0)                  if (iStreams < 0)
264                      throw Exception("Maximum disk streams for an engine cannot be set lower than 0");                      throw Exception("Maximum disk streams for an engine cannot be set lower than 0");
265    
# Line 256  namespace LinuxSampler { Line 274  namespace LinuxSampler {
274                  ResumeAll();                  ResumeAll();
275              }              }
276    
277              virtual String DiskStreamBufferFillBytes() { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; }              virtual String DiskStreamBufferFillBytes() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; }
278              virtual String DiskStreamBufferFillPercentage() { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; }              virtual String DiskStreamBufferFillPercentage() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; }
279              virtual InstrumentManager* GetInstrumentManager() { return &instruments; }              virtual InstrumentManager* GetInstrumentManager() OVERRIDE { return &instruments; }
280    
281              /**              /**
282               * Connect this engine instance with the given audio output device.               * Connect this engine instance with the given audio output device.
# Line 269  namespace LinuxSampler { Line 287  namespace LinuxSampler {
287               *               *
288               * @param pAudioOut - audio output device to connect to               * @param pAudioOut - audio output device to connect to
289               */               */
290              virtual void Connect(AudioOutputDevice* pAudioOut) {              virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE {
291                  // caution: don't ignore if connecting to the same device here,                  // caution: don't ignore if connecting to the same device here,
292                  // because otherwise SetMaxDiskStreams() implementation won't work anymore!                  // because otherwise SetMaxDiskStreams() implementation won't work anymore!
293    
# Line 299  namespace LinuxSampler { Line 317  namespace LinuxSampler {
317                      // lower minimum release time                      // lower minimum release time
318                      const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate;                      const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate;
319                      for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {                      for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
320                          iterVoice->pEG1->CalculateFadeOutCoeff(minReleaseTime, SampleRate);                          iterVoice->CalculateFadeOutCoeff(minReleaseTime, SampleRate);
321                      }                      }
322                      pVoicePool->clear();                      pVoicePool->clear();
323                  }                  }
# Line 332  namespace LinuxSampler { Line 350  namespace LinuxSampler {
350                  pDiskThread->StartThread();                  pDiskThread->StartThread();
351                  dmsg(1,("OK\n"));                  dmsg(1,("OK\n"));
352    
353                    bool printEqInfo = true;
354                  for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {                  for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) {
355                      if (!iterVoice->pDiskThread) {                      if (!iterVoice->pDiskThread) {
356                          dmsg(0,("Engine -> voice::trigger: !pDiskThread\n"));                          dmsg(0,("Engine -> voice::trigger: !pDiskThread\n"));
357                          exit(EXIT_FAILURE);                          exit(EXIT_FAILURE);
358                      }                      }
359                        
360                        iterVoice->CreateEq();
361                        
362                        if(printEqInfo) {
363                            iterVoice->PrintEqInfo();
364                            printEqInfo = false;
365                        }
366                  }                  }
367                  pVoicePool->clear();                  pVoicePool->clear();
368                    
369                    // (re)create dedicated voice audio buffers
370                    //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
371                    if (pDedicatedVoiceChannelLeft)  delete pDedicatedVoiceChannelLeft;
372                    if (pDedicatedVoiceChannelRight) delete pDedicatedVoiceChannelRight;
373                    pDedicatedVoiceChannelLeft  = new AudioChannel(0, MaxSamplesPerCycle);
374                    pDedicatedVoiceChannelRight = new AudioChannel(1, MaxSamplesPerCycle);
375                }
376            
377                // Implementattion for abstract method derived from Engine.
378                virtual void ReconnectAudioOutputDevice() OVERRIDE {
379                    SuspendAll();
380                    if (pAudioOutputDevice) Connect(pAudioOutputDevice);
381                    ResumeAll();
382              }              }
383    
384              /**              /**
# Line 393  namespace LinuxSampler { Line 433  namespace LinuxSampler {
433               * @param pRegion - region the engine shall stop using               * @param pRegion - region the engine shall stop using
434               */               */
435              virtual void Suspend(RR* pRegion) {              virtual void Suspend(RR* pRegion) {
436                  dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion));                  dmsg(2,("EngineBase: Suspending Region %p ...\n",(void*)pRegion));
437                  SuspendedRegionsMutex.Lock();                  {
438                  SuspensionChangeOngoing.Set(true);                      LockGuard lock(SuspendedRegionsMutex);
439                  pPendingRegionSuspension = pRegion;                      SuspensionChangeOngoing.Set(true);
440                  SuspensionChangeOngoing.WaitAndUnlockIf(true);                      pPendingRegionSuspension = pRegion;
441                  SuspendedRegionsMutex.Unlock();                      SuspensionChangeOngoing.WaitAndUnlockIf(true);
442                  dmsg(2,("EngineBase: Region %x suspended.",pRegion));                  }
443                    dmsg(2,("EngineBase: Region %p suspended.",(void*)pRegion));
444              }              }
445    
446              /**              /**
# Line 409  namespace LinuxSampler { Line 450  namespace LinuxSampler {
450               * @param pRegion - region the engine shall be allowed to use again               * @param pRegion - region the engine shall be allowed to use again
451               */               */
452              virtual void Resume(RR* pRegion) {              virtual void Resume(RR* pRegion) {
453                  dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion));                  dmsg(2,("EngineBase: Resuming Region %p ...\n",(void*)pRegion));
454                  SuspendedRegionsMutex.Lock();                  {
455                  SuspensionChangeOngoing.Set(true);                      LockGuard lock(SuspendedRegionsMutex);
456                  pPendingRegionResumption = pRegion;                      SuspensionChangeOngoing.Set(true);
457                  SuspensionChangeOngoing.WaitAndUnlockIf(true);                      pPendingRegionResumption = pRegion;
458                  SuspendedRegionsMutex.Unlock();                      SuspensionChangeOngoing.WaitAndUnlockIf(true);
459                  dmsg(2,("EngineBase: Region %x resumed.\n",pRegion));                  }
460                    dmsg(2,("EngineBase: Region %p resumed.\n",(void*)pRegion));
461              }              }
462    
463              virtual void ResetSuspendedRegions() {              virtual void ResetSuspendedRegions() {
# Line 530  namespace LinuxSampler { Line 572  namespace LinuxSampler {
572    
573              //friend class EngineChannelBase<V, R, I>;              //friend class EngineChannelBase<V, R, I>;
574    
575                static IM instruments;
576    
577          protected:          protected:
578              class SuspensionVoiceHandler : public MidiKeyboardManager<V>::VoiceHandler {              class SuspensionVoiceHandler : public MidiKeyboardManager<V>::VoiceHandler {
579              public:              public:
580                  int PendingStreamDeletions;                  int PendingStreamDeletions;
581                  RR* pPendingRegionSuspension;                  RR* pPendingRegionSuspension;
582    
583                  SuspensionVoiceHandler(RR* pPendingRegionSuspension) {                  SuspensionVoiceHandler(RR* pPendingRegionSuspension) {
584                      PendingStreamDeletions = 0;                      PendingStreamDeletions = 0;
585                      this->pPendingRegionSuspension = pPendingRegionSuspension;                      this->pPendingRegionSuspension = pPendingRegionSuspension;
586                  }                  }
587    
588                  virtual bool Process(MidiKey* pMidiKey) {                  virtual bool Process(MidiKey* pMidiKey) OVERRIDE {
589                      VoiceIterator itVoice = pMidiKey->pActiveVoices->first();                      VoiceIterator itVoice = pMidiKey->pActiveVoices->first();
590                      // if current key is not associated with this region, skip this key                      // if current key is not associated with this region, skip this key
591                      if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false;                      if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false;
# Line 548  namespace LinuxSampler { Line 593  namespace LinuxSampler {
593                      return true;                      return true;
594                  }                  }
595    
596                  virtual void Process(VoiceIterator& itVoice) {                  virtual void Process(VoiceIterator& itVoice) OVERRIDE {
597                      // request a notification from disk thread side for stream deletion                      // request a notification from disk thread side for stream deletion
598                      const Stream::Handle hStream = itVoice->KillImmediately(true);                      const Stream::Handle hStream = itVoice->KillImmediately(true);
599                      if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream                      if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream
# Line 558  namespace LinuxSampler { Line 603  namespace LinuxSampler {
603                  }                  }
604              };              };
605    
             static IM instruments;  
   
606              Pool<R*>* pRegionPool[2]; ///< Double buffered pool, used by the engine channels to keep track of regions in use.              Pool<R*>* pRegionPool[2]; ///< Double buffered pool, used by the engine channels to keep track of regions in use.
607              int       MinFadeOutSamples;     ///< The number of samples needed to make an instant fade out (e.g. for voice stealing) without leading to clicks.              int       MinFadeOutSamples;     ///< The number of samples needed to make an instant fade out (e.g. for voice stealing) without leading to clicks.
608              D*        pDiskThread;              D*        pDiskThread;
# Line 589  namespace LinuxSampler { Line 632  namespace LinuxSampler {
632                  AbstractEngineChannel* pChannel = static_cast<AbstractEngineChannel*>(pEngineChannel);                  AbstractEngineChannel* pChannel = static_cast<AbstractEngineChannel*>(pEngineChannel);
633                  pChannel->ImportEvents(Samples);                  pChannel->ImportEvents(Samples);
634    
635                  // process events                  // if a valid real-time instrument script is loaded, pre-process
636                    // the event list by running the script now, since the script
637                    // might filter events or add new ones for this cycle
638                    if (pChannel->pScript) {
639                        const sched_time_t fragmentEndTime = pEventGenerator->schedTimeAtCurrentFragmentEnd();
640    
641                        // resume suspended script executions been scheduled for
642                        // this audio fragment cycle (which were suspended in a
643                        // previous audio fragment cycle)
644                        ProcessSuspendedScriptEvents(pChannel, fragmentEndTime);
645    
646                        // spawn new script executions for the new MIDI events of
647                        // this audio fragment cycle
648                        //
649                        // FIXME: it would probably be better to just schedule newly spawned script executions here and then execute them altogether with already suspended ones all at once in order of all their scheduled timing
650                        for (RTList<Event>::Iterator itEvent = pChannel->pEvents->first(),
651                            end = pChannel->pEvents->end(); itEvent != end; ++itEvent)
652                        {
653                            switch (itEvent->Type) {
654                                case Event::type_note_on:
655                                    if (pChannel->pScript->handlerNote)
656                                        ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerNote);
657                                    break;
658                                case Event::type_note_off:
659                                    if (pChannel->pScript->handlerRelease)
660                                        ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerRelease);
661                                    break;
662                                case Event::type_control_change:
663                                case Event::type_channel_pressure:
664                                case Event::type_pitchbend:
665                                    if (pChannel->pScript->handlerController)
666                                        ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerController);                            
667                                    break;
668                                case Event::type_note_pressure:
669                                    //TODO: ...
670                                    break;
671                            }
672                        }
673    
674                        // this has to be run again, since the newly spawned scripts
675                        // above may have cause suspended scripts that must be
676                        // resumed within this same audio fragment cycle
677                        //
678                        // FIXME: see FIXME comment above
679                        ProcessSuspendedScriptEvents(pChannel, fragmentEndTime);
680                    }
681    
682                    // if there are any delayed events scheduled for the current
683                    // audio fragment cycle, then move and sort them into the main
684                    // event list
685                    if (!pChannel->delayedEvents.queue.isEmpty()) {
686                        dmsg(5,("Engine: There are delayed MIDI events (total queue size: %d) ...\n", pChannel->delayedEvents.queue.size()));
687                        const sched_time_t fragmentEndTime = pEventGenerator->schedTimeAtCurrentFragmentEnd();
688                        RTList<Event>::Iterator itEvent = pChannel->pEvents->first();
689                        while (true) {
690                            RTList<ScheduledEvent>::Iterator itDelayedEventNode =
691                                pEventGenerator->popNextScheduledEvent(
692                                    pChannel->delayedEvents.queue,
693                                    pChannel->delayedEvents.schedulerNodes,
694                                    fragmentEndTime
695                                );
696                            if (!itDelayedEventNode) break;
697                            // get the actual delayed event object and free the used scheduler node
698                            RTList<Event>::Iterator itDelayedEvent = itDelayedEventNode->itEvent;
699                            pChannel->delayedEvents.schedulerNodes.free(itDelayedEventNode);
700                            if (!itDelayedEvent) { // should never happen, but just to be sure ...
701                                dmsg(1,("Engine: Oops, invalid delayed event!\n"));
702                                continue;
703                            }
704                            // skip all events on main event list which have a time
705                            // before (or equal to) the delayed event to be inserted
706                            for (; itEvent && itEvent->FragmentPos() <= itDelayedEvent->FragmentPos();
707                                 ++itEvent);
708                            // now move delayed event from delayedEvents.pList to
709                            // the current position on the main event list
710                            itEvent = itDelayedEvent.moveBefore(itEvent);
711                            dmsg(5,("Engine: Inserted event of type %d into main event list (queue size: %d).\n", itEvent->Type, pChannel->delayedEvents.queue.size()));
712                        }
713                        dmsg(5,("Engine: End of delayed events (total queue size: %d).\n", pChannel->delayedEvents.queue.size()));
714                    }
715    
716                    // now process all events regularly
717                  {                  {
718                      RTList<Event>::Iterator itEvent = pChannel->pEvents->first();                      RTList<Event>::Iterator itEvent = pChannel->pEvents->first();
719                      RTList<Event>::Iterator end     = pChannel->pEvents->end();                      RTList<Event>::Iterator end     = pChannel->pEvents->end();
# Line 607  namespace LinuxSampler { Line 731  namespace LinuxSampler {
731                                  dmsg(5,("Engine: MIDI CC received\n"));                                  dmsg(5,("Engine: MIDI CC received\n"));
732                                  ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent);                                  ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent);
733                                  break;                                  break;
734                                case Event::type_channel_pressure:
735                                    dmsg(5,("Engine: MIDI Chan. Pressure received\n"));
736                                    ProcessChannelPressure((EngineChannel*)itEvent->pEngineChannel, itEvent);
737                                    break;
738                                case Event::type_note_pressure:
739                                    dmsg(5,("Engine: MIDI Note Pressure received\n"));
740                                    ProcessPolyphonicKeyPressure((EngineChannel*)itEvent->pEngineChannel, itEvent);
741                                    break;
742                              case Event::type_pitchbend:                              case Event::type_pitchbend:
743                                  dmsg(5,("Engine: Pitchbend received\n"));                                  dmsg(5,("Engine: Pitchbend received\n"));
744                                  ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent);                                  ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent);
# Line 624  namespace LinuxSampler { Line 756  namespace LinuxSampler {
756              }              }
757    
758              /**              /**
759                 * Run all suspended script execution instances which are scheduled
760                 * to be resumed for the current audio fragment cycle.
761                 *
762                 * @param pChannel - engine channel on which suspended events occurred
763                 */
764                void ProcessSuspendedScriptEvents(AbstractEngineChannel* pChannel, const sched_time_t fragmentEndTime) {
765                    while (true) {
766                        RTList<ScriptEvent>::Iterator itEvent =
767                            pEventGenerator->popNextScheduledScriptEvent(
768                                pChannel->pScript->suspendedEvents,
769                                *pChannel->pScript->pEvents, fragmentEndTime
770                            );
771                        if (!itEvent) break;
772                        ResumeScriptEvent(pChannel, itEvent);
773                    }
774                }
775    
776                /** @brief Call instrument script's event handler for this event.
777                 *
778                 * Causes a new execution instance of the currently loaded real-time
779                 * instrument script's event handler (callback) to be spawned for
780                 * the given MIDI event.
781                 *
782                 * @param pChannel - engine channel on which the MIDI event occurred
783                 * @param itEvent - MIDI event that causes this new script execution
784                 * @param pEventHandler - script's event handler to be executed
785                 */
786                void ProcessEventByScript(AbstractEngineChannel* pChannel, RTList<Event>::Iterator& itEvent, VMEventHandler* pEventHandler) {
787                    const int key = itEvent->Param.Note.Key; // even if this is not a note on/off event, accessing it does not mean any harm
788                    // check if polyphonic data is passed from "note" to "release"
789                    // script event handlers
790                    if (pEventHandler == pChannel->pScript->handlerRelease &&
791                        pChannel->pScript->handlerNote &&
792                        pChannel->pScript->handlerNote->isPolyphonic() &&
793                        pChannel->pScript->handlerRelease->isPolyphonic() &&
794                        !pChannel->pScript->pKeyEvents[key]->isEmpty())
795                    {
796                        // polyphonic variable data is used/passed from "note" to
797                        // "release" script callback, so we have to recycle the
798                        // original "note on" script event(s)
799                        RTList<ScriptEvent>::Iterator it  = pChannel->pScript->pKeyEvents[key]->first();
800                        RTList<ScriptEvent>::Iterator end = pChannel->pScript->pKeyEvents[key]->end();
801                        for (; it != end; ++it) {
802                            ProcessScriptEvent(
803                                pChannel, itEvent, pEventHandler, it
804                            );
805                        }
806                    } else {
807                        // no polyphonic data is used/passed from "note" to
808                        // "release" script callback, so just use a new fresh
809                        // script event object
810                        RTList<ScriptEvent>::Iterator itScriptEvent =
811                            pChannel->pScript->pEvents->allocAppend();
812                        ProcessScriptEvent(
813                            pChannel, itEvent, pEventHandler, itScriptEvent
814                        );
815                    }
816                }
817    
818                /** @brief Spawn new execution instance of an instrument script handler.
819                 *
820                 * Will be called to initiate a new execution of a real-time
821                 * instrument script event right from the start of the script's
822                 * respective handler. If script execution did not complete after
823                 * calling this method, the respective script exeuction is then
824                 * suspended and a call to ResumeScriptEvent() will be used next
825                 * time to continue its execution.
826                 *
827                 * @param pChannel - engine channel this script is running for
828                 * @param itEvent - event which caused execution of this script
829                 *                  event handler
830                 * @param pEventHandler - VM representation of event handler to be
831                 *                        executed
832                 * @param itScriptEvent - script event that shall be processed
833                 */
834                void ProcessScriptEvent(AbstractEngineChannel* pChannel, RTList<Event>::Iterator& itEvent, VMEventHandler* pEventHandler, RTList<ScriptEvent>::Iterator& itScriptEvent) {
835                    if (!itScriptEvent) return; // not a valid script event (i.e. because no free script event was left in the script event pool)
836    
837                    // fill the list of script handlers to be executed by this event
838                    int i = 0;
839                    itScriptEvent->handlers[i++] = pEventHandler; // actual event handler (i.e. note, controller)
840                    itScriptEvent->handlers[i] = NULL; // NULL termination of list
841    
842                    // initialize/reset other members
843                    itScriptEvent->cause = *itEvent;
844                    itScriptEvent->id = pEventPool->getID(itEvent);
845                    itScriptEvent->currentHandler = 0;
846                    itScriptEvent->executionSlices = 0;
847    
848                    // run script handler(s)
849                    VMExecStatus_t res = pScriptVM->exec(
850                        pChannel->pScript->parserContext, &*itScriptEvent
851                    );
852    
853                    // was the script suspended?
854                    if (res & VM_EXEC_SUSPENDED) { // script was suspended ...
855                        // in case the script was suspended, keep it on the allocated
856                        // ScriptEvent list to be resume at the scheduled time in future,
857                        // additionally insert it into a sorted time queue
858                        pEventGenerator->scheduleAheadMicroSec(
859                            pChannel->pScript->suspendedEvents, // scheduler queue
860                            *itScriptEvent, // script event
861                            itScriptEvent->cause.FragmentPos(), // current time of script event (basis for its next execution)
862                            itScriptEvent->execCtx->suspensionTimeMicroseconds() // how long shall it be suspended
863                        );
864                    } else { // script execution has finished without 'suspended' status ...
865                        // if "polyphonic" variable data is passed from script's
866                        // "note" event handler to its "release" event handler, then
867                        // the script event must be kept and recycled for the later
868                        // occuring "release" script event ...
869                        if (pEventHandler == pChannel->pScript->handlerNote &&
870                            pChannel->pScript->handlerRelease &&
871                            pChannel->pScript->handlerNote->isPolyphonic() &&
872                            pChannel->pScript->handlerRelease->isPolyphonic())
873                        {
874                            const int key = itEvent->Param.Note.Key;
875                            itScriptEvent.moveToEndOf(pChannel->pScript->pKeyEvents[key & 127]);
876                        } else {
877                            // ... otherwise if no polyphonic data is passed and
878                            // script's execution has finished without suspension
879                            // status, then free the script event for a new future
880                            // script event to be triggered from start
881                            pChannel->pScript->pEvents->free(itScriptEvent);
882                        }
883                    }
884                }
885    
886                /** @brief Resume execution of instrument script.
887                 *
888                 * Will be called to resume execution of a real-time instrument
889                 * script event which has been suspended in a previous audio
890                 * fragment cycle.
891                 *
892                 * Script execution might be suspended for various reasons. Usually
893                 * a script will be suspended if the script called the built-in
894                 * "wait()" function, but it might also be suspended automatically
895                 * if the script took too much execution time in an audio fragment
896                 * cycle. So in the latter case automatic suspension is performed in
897                 * order to avoid harm for the sampler's overall real-time
898                 * requirements.
899                 *
900                 * @param pChannel - engine channel this script is running for
901                 * @param itScriptEvent - script execution that shall be resumed
902                 */
903                void ResumeScriptEvent(AbstractEngineChannel* pChannel, RTList<ScriptEvent>::Iterator& itScriptEvent) {
904                    VMEventHandler* handler = itScriptEvent->handlers[itScriptEvent->currentHandler];
905    
906                    // run script
907                    VMExecStatus_t res = pScriptVM->exec(
908                        pChannel->pScript->parserContext, &*itScriptEvent
909                    );
910    
911                    // was the script suspended?
912                    if (res & VM_EXEC_SUSPENDED) {
913                        // in case the script was suspended, keep it on the allocated
914                        // ScriptEvent list to be resume at the scheduled time in future,
915                        // additionally insert it into a sorted time queue
916                        pEventGenerator->scheduleAheadMicroSec(
917                            pChannel->pScript->suspendedEvents, // scheduler queue
918                            *itScriptEvent, // script event
919                            itScriptEvent->cause.FragmentPos(), // current time of script event (basis for its next execution)
920                            itScriptEvent->execCtx->suspensionTimeMicroseconds() // how long shall it be suspended
921                        );
922                    } else { // script execution has finished without 'suspended' status ...
923                        // if "polyphonic" variable data is passed from script's
924                        // "note" event handler to its "release" event handler, then
925                        // the script event must be kept and recycled for the later
926                        // occuring "release" script event ...
927                        if (handler && handler == pChannel->pScript->handlerNote &&
928                            pChannel->pScript->handlerRelease &&
929                            pChannel->pScript->handlerNote->isPolyphonic() &&
930                            pChannel->pScript->handlerRelease->isPolyphonic())
931                        {
932                            const int key = itScriptEvent->cause.Param.Note.Key;
933                            itScriptEvent.moveToEndOf(pChannel->pScript->pKeyEvents[key & 127]);
934                        } else {
935                            // ... otherwise if no polyphonic data is passed and
936                            // script's execution has finished without suspension
937                            // status, then free the script event for a new future
938                            // script event to be triggered from start
939                            pChannel->pScript->pEvents->free(itScriptEvent);
940                        }
941                    }
942                }
943    
944                /**
945               *  Will be called by LaunchVoice() method in case there are no free               *  Will be called by LaunchVoice() method in case there are no free
946               *  voices left. This method will select and kill one old voice for               *  voices left. This method will select and kill one old voice for
947               *  voice stealing and postpone the note-on event until the selected               *  voice stealing and postpone the note-on event until the selected
948               *  voice actually died.               *  voice actually died.
949               *               *
950               *  @param pEngineChannel - engine channel on which this event occured on               *  @param pEngineChannel - engine channel on which this event occurred on
951               *  @param itNoteOnEvent - key, velocity and time stamp of the event               *  @param itNoteOnEvent - key, velocity and time stamp of the event
952               *  @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing               *  @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing
953               */               */
# Line 755  namespace LinuxSampler { Line 1073  namespace LinuxSampler {
1073                          dmsg(5,("Engine: instrument change command received\n"));                          dmsg(5,("Engine: instrument change command received\n"));
1074                          cmd.bChangeInstrument = false;                          cmd.bChangeInstrument = false;
1075                          pEngineChannel->pInstrument = cmd.pInstrument;                          pEngineChannel->pInstrument = cmd.pInstrument;
1076                            pEngineChannel->pScript =
1077                                cmd.pScript->bHasValidScript ? cmd.pScript : NULL;
1078                          instrumentChanged = true;                          instrumentChanged = true;
1079    
1080                          pEngineChannel->MarkAllActiveVoicesAsOrphans();                          pEngineChannel->MarkAllActiveVoicesAsOrphans();
1081    
1082                            // the script's "init" event handler is only executed
1083                            // once (when the script is loaded or reloaded)
1084                            if (pEngineChannel->pScript && pEngineChannel->pScript->handlerInit) {
1085                                RTList<ScriptEvent>::Iterator itScriptEvent =
1086                                    pEngineChannel->pScript->pEvents->allocAppend();
1087    
1088                                itScriptEvent->cause.pEngineChannel = pEngineChannel;
1089                                itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit;
1090                                itScriptEvent->handlers[1] = NULL;
1091    
1092                                VMExecStatus_t res = pScriptVM->exec(
1093                                    pEngineChannel->pScript->parserContext, &*itScriptEvent
1094                                );
1095    
1096                                pEngineChannel->pScript->pEvents->free(itScriptEvent);
1097                            }
1098                      }                      }
1099                  }                  }
1100    
# Line 765  namespace LinuxSampler { Line 1102  namespace LinuxSampler {
1102                      //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                      //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
1103                      ResetSuspendedRegions();                      ResetSuspendedRegions();
1104                  }                  }
   
                 for (int i = 0; i < engineChannels.size(); i++) {  
                     EngineChannelBase<V, R, I>* channel =  
                         static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]);  
                     channel->InstrumentChangeCommandReader.Unlock();  
                 }  
1105              }              }
1106    
1107              /**              /**
# Line 854  namespace LinuxSampler { Line 1185  namespace LinuxSampler {
1185                   pChannel->FreeAllInactiveKyes();                   pChannel->FreeAllInactiveKyes();
1186    
1187                  // empty the engine channel's own event lists                  // empty the engine channel's own event lists
1188                  pChannel->ClearEventLists();                  // (only events of the current audio fragment cycle)
1189                    pChannel->ClearEventListsOfCurrentFragment();
1190              }              }
1191    
1192                /**
1193                 * Process MIDI control change events with hard coded behavior,
1194                 * that is controllers whose behavior is defined independently
1195                 * of the actual sampler engine type and instrument.
1196                 *
1197                 * @param pEngineChannel - engine channel on which the MIDI CC event was received
1198                 * @param itControlChangeEvent - the actual MIDI CC event
1199                 */
1200              void ProcessHardcodedControllers (              void ProcessHardcodedControllers (
1201                  EngineChannel*          pEngineChannel,                  EngineChannel*          pEngineChannel,
1202                  Pool<Event>::Iterator&  itControlChangeEvent                  Pool<Event>::Iterator&  itControlChangeEvent
# Line 869  namespace LinuxSampler { Line 1209  namespace LinuxSampler {
1209                          pChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN;                          pChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN;
1210                          break;                          break;
1211                      }                      }
1212                      case 6: { // data entry (currently only used for RPN controllers)                      case 6: { // data entry (currently only used for RPN and NRPN controllers)
1213                          if (pChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones                          //dmsg(1,("DATA ENTRY %d\n", itControlChangeEvent->Param.CC.Value));
1214                              int transpose = (int) itControlChangeEvent->Param.CC.Value - 64;                          if (pChannel->GetMidiRpnController() >= 0) { // RPN controller number was sent previously ...
1215                              // limit to +- two octaves for now                              dmsg(4,("Guess it's an RPN ...\n"));
1216                              transpose = RTMath::Min(transpose,  24);                              if (pChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones
1217                              transpose = RTMath::Max(transpose, -24);                                  int transpose = (int) itControlChangeEvent->Param.CC.Value - 64;
1218                              pChannel->GlobalTranspose = transpose;                                  // limit to +- two octaves for now
1219                              // workaround, so we won't have hanging notes                                  transpose = RTMath::Min(transpose,  24);
1220                              pChannel->ReleaseAllVoices(itControlChangeEvent);                                  transpose = RTMath::Max(transpose, -24);
1221                                    pChannel->GlobalTranspose = transpose;
1222                                    // workaround, so we won't have hanging notes
1223                                    pChannel->ReleaseAllVoices(itControlChangeEvent);
1224                                }
1225                                // to prevent other MIDI CC #6 messages to be misenterpreted as RPN controller data
1226                                pChannel->ResetMidiRpnController();
1227                            } else if (pChannel->GetMidiNrpnController() >= 0) { // NRPN controller number was sent previously ...
1228                                dmsg(4,("Guess it's an NRPN ...\n"));
1229                                const int NrpnCtrlMSB = pChannel->GetMidiNrpnController() >> 8;
1230                                const int NrpnCtrlLSB = pChannel->GetMidiNrpnController() & 0xff;
1231                                dmsg(4,("NRPN MSB=%d LSB=%d Data=%d\n", NrpnCtrlMSB, NrpnCtrlLSB, itControlChangeEvent->Param.CC.Value));
1232                                switch (NrpnCtrlMSB) {
1233                                    case 0x1a: { // volume level of note (Roland GS NRPN)
1234                                        const uint note = NrpnCtrlLSB;
1235                                        const uint vol  = itControlChangeEvent->Param.CC.Value;
1236                                        dmsg(4,("Note Volume NRPN received (note=%d,vol=%d).\n", note, vol));
1237                                        if (note < 128 && vol < 128)
1238                                            pChannel->pMIDIKeyInfo[note].Volume = VolumeCurve[vol];
1239                                        break;
1240                                    }
1241                                    case 0x1c: { // panpot of note (Roland GS NRPN)
1242                                        const uint note = NrpnCtrlLSB;
1243                                        const uint pan  = itControlChangeEvent->Param.CC.Value;
1244                                        dmsg(4,("Note Pan NRPN received (note=%d,pan=%d).\n", note, pan));
1245                                        if (note < 128 && pan < 128) {
1246                                            pChannel->pMIDIKeyInfo[note].PanLeft  = PanCurve[128 - pan];
1247                                            pChannel->pMIDIKeyInfo[note].PanRight = PanCurve[pan];
1248                                        }
1249                                        break;
1250                                    }
1251                                    case 0x1d: { // reverb send of note (Roland GS NRPN)
1252                                        const uint note = NrpnCtrlLSB;
1253                                        const float reverb = float(itControlChangeEvent->Param.CC.Value) / 127.0f;
1254                                        dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%f).\n", note, reverb));
1255                                        if (note < 128)
1256                                            pChannel->pMIDIKeyInfo[note].ReverbSend = reverb;
1257                                        break;
1258                                    }
1259                                    case 0x1e: { // chorus send of note (Roland GS NRPN)
1260                                        const uint note = NrpnCtrlLSB;
1261                                        const float chorus = float(itControlChangeEvent->Param.CC.Value) / 127.0f;
1262                                        dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%f).\n", note, chorus));
1263                                        if (note < 128)
1264                                            pChannel->pMIDIKeyInfo[note].ChorusSend = chorus;
1265                                        break;
1266                                    }
1267                                }
1268                                // to prevent other MIDI CC #6 messages to be misenterpreted as NRPN controller data
1269                                pChannel->ResetMidiNrpnController();
1270                          }                          }
                         // to avoid other MIDI CC #6 messages to be misenterpreted as RPN controller data  
                         pChannel->ResetMidiRpnController();  
1271                          break;                          break;
1272                      }                      }
1273                      case 7: { // volume                      case 7: { // volume
# Line 891  namespace LinuxSampler { Line 1278  namespace LinuxSampler {
1278                      }                      }
1279                      case 10: { // panpot                      case 10: { // panpot
1280                          //TODO: not sample accurate yet                          //TODO: not sample accurate yet
                         pChannel->GlobalPanLeft  = PanCurve[128 - itControlChangeEvent->Param.CC.Value];  
                         pChannel->GlobalPanRight = PanCurve[itControlChangeEvent->Param.CC.Value];  
1281                          pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value;                          pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value;
1282                          break;                          break;
1283                      }                      }
# Line 969  namespace LinuxSampler { Line 1354  namespace LinuxSampler {
1354                          }                          }
1355                          break;                          break;
1356                      }                      }
1357                        case 98: { // NRPN controller LSB
1358                            dmsg(4,("NRPN LSB %d\n", itControlChangeEvent->Param.CC.Value));
1359                            pEngineChannel->SetMidiNrpnControllerLsb(itControlChangeEvent->Param.CC.Value);
1360                            break;
1361                        }
1362                        case 99: { // NRPN controller MSB
1363                            dmsg(4,("NRPN MSB %d\n", itControlChangeEvent->Param.CC.Value));
1364                            pEngineChannel->SetMidiNrpnControllerMsb(itControlChangeEvent->Param.CC.Value);
1365                            break;
1366                        }
1367                      case 100: { // RPN controller LSB                      case 100: { // RPN controller LSB
1368                            dmsg(4,("RPN LSB %d\n", itControlChangeEvent->Param.CC.Value));
1369                          pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value);                          pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value);
1370                          break;                          break;
1371                      }                      }
1372                      case 101: { // RPN controller MSB                      case 101: { // RPN controller MSB
1373                            dmsg(4,("RPN MSB %d\n", itControlChangeEvent->Param.CC.Value));
1374                          pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value);                          pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value);
1375                          break;                          break;
1376                      }                      }
# Line 1015  namespace LinuxSampler { Line 1412  namespace LinuxSampler {
1412              /**              /**
1413               *  Assigns and triggers a new voice for the respective MIDI key.               *  Assigns and triggers a new voice for the respective MIDI key.
1414               *               *
1415               *  @param pEngineChannel - engine channel on which this event occured on               *  @param pEngineChannel - engine channel on which this event occurred on
1416               *  @param itNoteOnEvent - key, velocity and time stamp of the event               *  @param itNoteOnEvent - key, velocity and time stamp of the event
1417               */               */
1418              virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) {              virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) {
# Line 1076  namespace LinuxSampler { Line 1473  namespace LinuxSampler {
1473                  pChannel->ProcessKeySwitchChange(key);                  pChannel->ProcessKeySwitchChange(key);
1474    
1475                  pKey->KeyPressed = true; // the MIDI key was now pressed down                  pKey->KeyPressed = true; // the MIDI key was now pressed down
1476                    pChannel->KeyDown[key] = true; // just used as built-in %KEY_DOWN script variable
1477                  pKey->Velocity   = itNoteOnEventOnKeyList->Param.Note.Velocity;                  pKey->Velocity   = itNoteOnEventOnKeyList->Param.Note.Velocity;
1478                  pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length                  pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length
1479    
# Line 1126  namespace LinuxSampler { Line 1524  namespace LinuxSampler {
1524               *  sustain pedal will be released or voice turned inactive by itself (e.g.               *  sustain pedal will be released or voice turned inactive by itself (e.g.
1525               *  due to completion of sample playback).               *  due to completion of sample playback).
1526               *               *
1527               *  @param pEngineChannel - engine channel on which this event occured on               *  @param pEngineChannel - engine channel on which this event occurred on
1528               *  @param itNoteOffEvent - key, velocity and time stamp of the event               *  @param itNoteOffEvent - key, velocity and time stamp of the event
1529               */               */
1530              virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOffEvent) {              virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOffEvent) {
# Line 1152  namespace LinuxSampler { Line 1550  namespace LinuxSampler {
1550                  #endif                  #endif
1551    
1552                  pKey->KeyPressed = false; // the MIDI key was now released                  pKey->KeyPressed = false; // the MIDI key was now released
1553                    pChannel->KeyDown[iKey] = false; // just used as built-in %KEY_DOWN script variable
1554    
1555                  // move event to the key's own event list                  // move event to the key's own event list
1556                  RTList<Event>::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents);                  RTList<Event>::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents);
# Line 1231  namespace LinuxSampler { Line 1630  namespace LinuxSampler {
1630               *  control and status variables. This method is protected by a mutex.               *  control and status variables. This method is protected by a mutex.
1631               */               */
1632              virtual void ResetInternal() {              virtual void ResetInternal() {
1633                  ResetInternalMutex.Lock();                  LockGuard lock(ResetInternalMutex);
1634    
1635                  // make sure that the engine does not get any sysex messages                  // make sure that the engine does not get any sysex messages
1636                  // while it's reseting                  // while it's reseting
# Line 1253  namespace LinuxSampler { Line 1652  namespace LinuxSampler {
1652                  }                  }
1653                  pVoicePool->clear();                  pVoicePool->clear();
1654    
1655                    // reset all engine channels
1656                    for (int i = 0; i < engineChannels.size(); i++) {
1657                        AbstractEngineChannel* pEngineChannel =
1658                            static_cast<AbstractEngineChannel*>(engineChannels[i]);
1659                        pEngineChannel->ResetInternal(false/*don't reset engine*/);
1660                    }
1661    
1662                  // reset disk thread                  // reset disk thread
1663                  if (pDiskThread) pDiskThread->Reset();                  if (pDiskThread) pDiskThread->Reset();
1664    
# Line 1260  namespace LinuxSampler { Line 1666  namespace LinuxSampler {
1666                  pEventQueue->init();                  pEventQueue->init();
1667                  pSysexBuffer->init();                  pSysexBuffer->init();
1668                  if (sysexDisabled) MidiInputPort::AddSysexListener(this);                  if (sysexDisabled) MidiInputPort::AddSysexListener(this);
                 ResetInternalMutex.Unlock();  
1669              }              }
1670    
1671              /**              /**
# Line 1282  namespace LinuxSampler { Line 1687  namespace LinuxSampler {
1687               *  called by the ProcessNoteOn() method and by the voices itself               *  called by the ProcessNoteOn() method and by the voices itself
1688               *  (e.g. to spawn further voices on the same key for layered sounds).               *  (e.g. to spawn further voices on the same key for layered sounds).
1689               *               *
1690               *  @param pEngineChannel      - engine channel on which this event occured on               *  @param pEngineChannel      - engine channel on which this event occurred on
1691               *  @param itNoteOnEvent       - key, velocity and time stamp of the event               *  @param itNoteOnEvent       - key, velocity and time stamp of the event
1692               *  @param iLayer              - layer index for the new voice (optional - only               *  @param iLayer              - layer index for the new voice (optional - only
1693               *                               in case of layered sounds of course)               *                               in case of layered sounds of course)
# Line 1356  namespace LinuxSampler { Line 1761  namespace LinuxSampler {
1761    
1762                  return -1;                  return -1;
1763              }              }
1764                
1765                /**
1766                 * Checks whether scale tuning setting has been changed since last
1767                 * time this method was called, if yes, it recalculates the pitch
1768                 * for all active voices.
1769                 */
1770                void ProcessScaleTuningChange() {
1771                    const bool changed = ScaleTuningChanged.readAndReset();
1772                    if (!changed) return;
1773                    
1774                    for (int i = 0; i < engineChannels.size(); i++) {
1775                        EngineChannelBase<V, R, I>* channel =
1776                            static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]);
1777                        channel->OnScaleTuningChanged();
1778                    }
1779                }
1780    
1781          private:          private:
1782              Pool<V>*    pVoicePool;            ///< Contains all voices that can be activated.              Pool<V>*    pVoicePool;            ///< Contains all voices that can be activated.

Legend:
Removed from v.2115  
changed lines
  Added in v.2871

  ViewVC Help
Powered by ViewVC