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

Annotation of /linuxsampler/trunk/src/engines/EngineChannelBase.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3054 - (hide annotations) (download) (as text)
Thu Dec 15 12:47:45 2016 UTC (7 years, 3 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 25485 byte(s)
* Fixed numerous compiler warnings.
* Bumped version (2.0.0.svn32).

1 iliev 2012 /***************************************************************************
2     * *
3     * LinuxSampler - modular, streaming capable sampler *
4     * *
5     * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck *
6 persson 2072 * Copyright (C) 2005-2008 Christian Schoenebeck *
7 schoenebeck 2871 * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev *
8     * Copyright (C) 2012-2016 Christian Schoenebeck and Andreas Persson *
9 iliev 2012 * *
10     * 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 *
12     * the Free Software Foundation; either version 2 of the License, or *
13     * (at your option) any later version. *
14     * *
15     * This program is distributed in the hope that it will be useful, *
16     * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18     * GNU General Public License for more details. *
19     * *
20     * You should have received a copy of the GNU General Public License *
21     * along with this program; if not, write to the Free Software *
22     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
23     * MA 02111-1307 USA *
24     ***************************************************************************/
25    
26     #ifndef __LS_ENGINECHANNELBASE_H__
27     #define __LS_ENGINECHANNELBASE_H__
28    
29     #include "AbstractEngineChannel.h"
30     #include "common/MidiKeyboardManager.h"
31     #include "common/Voice.h"
32     #include "../common/ResourceManager.h"
33    
34     namespace LinuxSampler {
35     /// Command used by the instrument loader thread to
36     /// request an instrument change on a channel.
37     template <class R /* Region */, class I /* Instrument */>
38     class InstrumentChangeCmd {
39     public:
40     bool bChangeInstrument; ///< Set to true by the loader when the channel should change instrument.
41     I* pInstrument; ///< The new instrument. Also used by the loader to read the previously loaded instrument.
42     RTList<R*>* pRegionsInUse; ///< List of dimension regions in use by the currently loaded instrument. Continuously updated by the audio thread.
43 schoenebeck 2659 InstrumentScript* pScript; ///< Instrument script to be executed for this instrument. This is never NULL, it is always a valid InstrumentScript pointer. Use InstrumentScript::bHasValidScript whether it reflects a valid instrument script to be executed.
44 iliev 2012 };
45    
46     template<class R>
47     class RegionPools {
48     public:
49     virtual Pool<R*>* GetRegionPool(int index) = 0;
50     };
51    
52     template<class V>
53 schoenebeck 2879 class NotePool {
54 iliev 2012 public:
55     virtual Pool<V>* GetVoicePool() = 0;
56 schoenebeck 2879 virtual Pool< Note<V> >* GetNotePool() = 0;
57     virtual Pool<note_id_t>* GetNodeIDPool() = 0;
58 iliev 2012 };
59    
60     template <class V /* Voice */, class R /* Region */, class I /* Instrument */>
61     class EngineChannelBase: public AbstractEngineChannel, public MidiKeyboardManager<V>, public ResourceConsumer<I> {
62     public:
63 schoenebeck 2884 typedef typename RTList< Note<V> >::Iterator NoteIterator;
64 iliev 2012 typedef typename RTList<R*>::Iterator RTListRegionIterator;
65     typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;
66    
67 schoenebeck 2613 virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE {
68     return this;
69     }
70    
71 iliev 2012 virtual void HandBack(I* Instrument) {
72     ResourceManager<InstrumentManager::instrument_id_t, I>* mgr =
73     dynamic_cast<ResourceManager<InstrumentManager::instrument_id_t, I>*>(pEngine->GetInstrumentManager());
74     mgr->HandBack(Instrument, this);
75     }
76    
77     virtual void ClearRegionsInUse() {
78     {
79     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
80 persson 2165 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
81     cmd.bChangeInstrument = false;
82 iliev 2012 }
83     {
84     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
85 persson 2165 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
86     cmd.bChangeInstrument = false;
87 iliev 2012 }
88     }
89    
90     virtual void ResetRegionsInUse(Pool<R*>* pRegionPool[]) {
91     DeleteRegionsInUse();
92     AllocateRegionsInUse(pRegionPool);
93     }
94    
95     virtual void DeleteRegionsInUse() {
96 schoenebeck 2612 RTList<R*>* previous = NULL; // prevent double free
97 iliev 2012 {
98     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
99 persson 2165 if (cmd.pRegionsInUse) {
100 schoenebeck 2612 previous = cmd.pRegionsInUse;
101 iliev 2012 delete cmd.pRegionsInUse;
102     cmd.pRegionsInUse = NULL;
103     }
104 persson 2165 cmd.bChangeInstrument = false;
105 iliev 2012 }
106     {
107     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
108 persson 2165 if (cmd.pRegionsInUse) {
109 schoenebeck 2612 if (cmd.pRegionsInUse != previous)
110     delete cmd.pRegionsInUse;
111 iliev 2012 cmd.pRegionsInUse = NULL;
112     }
113 persson 2165 cmd.bChangeInstrument = false;
114 iliev 2012 }
115     }
116    
117     virtual void AllocateRegionsInUse(Pool<R*>* pRegionPool[]) {
118     {
119     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
120     cmd.pRegionsInUse = new RTList<R*>(pRegionPool[0]);
121 persson 2165 cmd.bChangeInstrument = false;
122 iliev 2012 }
123     {
124     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
125     cmd.pRegionsInUse = new RTList<R*>(pRegionPool[1]);
126 persson 2165 cmd.bChangeInstrument = false;
127 iliev 2012 }
128     }
129    
130 schoenebeck 3054 virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE {
131 iliev 2012 if (pEngine) {
132     if (pEngine->pAudioOutputDevice == pAudioOut) return;
133     DisconnectAudioOutputDevice();
134     }
135 persson 2326 AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut);
136 persson 2427 {
137     LockGuard lock(EngineMutex);
138     pEngine = newEngine;
139     }
140 schoenebeck 2871 ResetInternal(false/*don't reset engine*/); // 'false' is error prone here, but the danger of recursion with 'true' would be worse, there could be a better solution though
141 iliev 2012 pEvents = new RTList<Event>(pEngine->pEventPool);
142 schoenebeck 2871 delayedEvents.pList = new RTList<Event>(pEngine->pEventPool);
143 iliev 2012
144     RegionPools<R>* pRegionPool = dynamic_cast<RegionPools<R>*>(pEngine);
145     // reset the instrument change command struct (need to be done
146     // twice, as it is double buffered)
147     {
148     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
149     cmd.pRegionsInUse = new RTList<R*>(pRegionPool->GetRegionPool(0));
150     cmd.pInstrument = 0;
151     cmd.bChangeInstrument = false;
152     }
153     {
154     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
155     cmd.pRegionsInUse = new RTList<R*>(pRegionPool->GetRegionPool(1));
156     cmd.pInstrument = 0;
157     cmd.bChangeInstrument = false;
158     }
159    
160     if (pInstrument != NULL) {
161     pInstrument = NULL;
162     InstrumentStat = -1;
163     InstrumentIdx = -1;
164     InstrumentIdxName = "";
165     InstrumentFile = "";
166     bStatusChanged = true;
167     }
168    
169 schoenebeck 2879 NotePool<V>* pNotePool = dynamic_cast<NotePool<V>*>(pEngine);
170     MidiKeyboardManager<V>::AllocateActiveNotesLists(
171     pNotePool->GetNotePool(),
172     pNotePool->GetVoicePool()
173     );
174     MidiKeyboardManager<V>::AllocateEventsLists(pEngine->pEventPool);
175 iliev 2012
176     AudioDeviceChannelLeft = 0;
177     AudioDeviceChannelRight = 1;
178     if (fxSends.empty()) { // render directly into the AudioDevice's output buffers
179     pChannelLeft = pAudioOut->Channel(AudioDeviceChannelLeft);
180     pChannelRight = pAudioOut->Channel(AudioDeviceChannelRight);
181     } else { // use local buffers for rendering and copy later
182     // ensure the local buffers have the correct size
183     if (pChannelLeft) delete pChannelLeft;
184     if (pChannelRight) delete pChannelRight;
185     pChannelLeft = new AudioChannel(0, pAudioOut->MaxSamplesPerCycle());
186     pChannelRight = new AudioChannel(1, pAudioOut->MaxSamplesPerCycle());
187     }
188     if (pEngine->EngineDisabled.GetUnsafe()) pEngine->Enable();
189     MidiInputPort::AddSysexListener(pEngine);
190     }
191    
192 schoenebeck 3054 virtual void DisconnectAudioOutputDevice() OVERRIDE {
193 iliev 2012 if (pEngine) { // if clause to prevent disconnect loops
194    
195 schoenebeck 2871 ResetInternal(false/*don't reset engine*/); // 'false' is error prone here, but the danger of recursion with 'true' would be worse, there could be a better solution though
196 iliev 2012
197     DeleteRegionsInUse();
198 schoenebeck 2612 UnloadScriptInUse();
199 iliev 2012
200     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
201     if (cmd.pInstrument) {
202     // release the currently loaded instrument
203     HandBack(cmd.pInstrument);
204     }
205    
206     if (pEvents) {
207     delete pEvents;
208     pEvents = NULL;
209     }
210 schoenebeck 2871 if (delayedEvents.pList) {
211     delete delayedEvents.pList;
212     delayedEvents.pList = NULL;
213     }
214 iliev 2012
215 schoenebeck 2879 MidiKeyboardManager<V>::DeleteActiveNotesLists();
216     MidiKeyboardManager<V>::DeleteEventsLists();
217 persson 2114 DeleteGroupEventLists();
218 iliev 2012
219     AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice;
220 persson 2427 {
221     LockGuard lock(EngineMutex);
222     pEngine = NULL;
223     }
224 iliev 2012 AbstractEngine::FreeEngine(this, oldAudioDevice);
225     AudioDeviceChannelLeft = -1;
226     AudioDeviceChannelRight = -1;
227     if (!fxSends.empty()) { // free the local rendering buffers
228     if (pChannelLeft) delete pChannelLeft;
229     if (pChannelRight) delete pChannelRight;
230     }
231     pChannelLeft = NULL;
232     pChannelRight = NULL;
233     }
234     }
235    
236     class ClearEventListsHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
237     public:
238     virtual bool Process(MidiKey* pMidiKey) { pMidiKey->pEvents->clear(); return false; }
239     };
240    
241 schoenebeck 2871 /**
242     * Free all events of the current audio fragment cycle. Calling
243     * this method will @b NOT free events scheduled past the current
244     * fragment's boundary! (@see AbstractEngineChannel::delayedEvents).
245     */
246     void ClearEventListsOfCurrentFragment() {
247 iliev 2012 pEvents->clear();
248     // empty MIDI key specific event lists
249     ClearEventListsHandler handler;
250 persson 2335 this->ProcessActiveVoices(&handler);
251 persson 2114
252     // empty exclusive group specific event lists
253 persson 2352 // (pInstrument == 0 could mean that LoadInstrument is
254     // building new group event lists, so we must check
255     // for that)
256     if (pInstrument) ClearGroupEventLists();
257 iliev 2012 }
258    
259     // implementation of abstract methods derived from interface class 'InstrumentConsumer'
260    
261     /**
262     * Will be called by the InstrumentResourceManager when the instrument
263     * we are currently using on this EngineChannel is going to be updated,
264     * so we can stop playback before that happens.
265     */
266 schoenebeck 2434 virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) OVERRIDE {
267 iliev 2012 dmsg(3,("EngineChannelBase: Received instrument update message.\n"));
268     if (pEngine) pEngine->DisableAndLock();
269 schoenebeck 2871 ResetInternal(false/*don't reset engine*/);
270 iliev 2012 this->pInstrument = NULL;
271     }
272    
273     /**
274     * Will be called by the InstrumentResourceManager when the instrument
275     * update process was completed, so we can continue with playback.
276     */
277 schoenebeck 2434 virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) OVERRIDE {
278 iliev 2012 this->pInstrument = pNewResource; //TODO: there are couple of engine parameters we should update here as well if the instrument was updated (see LoadInstrument())
279     if (pEngine) pEngine->Enable();
280     bStatusChanged = true; // status of engine has changed, so set notify flag
281     }
282    
283     /**
284     * Will be called by the InstrumentResourceManager on progress changes
285     * while loading or realoading an instrument for this EngineChannel.
286     *
287     * @param fProgress - current progress as value between 0.0 and 1.0
288     */
289 schoenebeck 2434 virtual void OnResourceProgress(float fProgress) OVERRIDE {
290 iliev 2012 this->InstrumentStat = int(fProgress * 100.0f);
291 persson 2837 dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat));
292 iliev 2012 bStatusChanged = true; // status of engine has changed, so set notify flag
293     }
294    
295 schoenebeck 2927 /**
296     * Called on sustain pedal up events to check and if required,
297     * launch release trigger voices on the respective active key.
298     *
299     * @param pEngineChannel - engine channel on which this event occurred on
300     * @param itEvent - release trigger event (contains note number)
301     */
302     virtual void ProcessReleaseTrigger(RTList<Event>::Iterator& itEvent) OVERRIDE {
303     if (!pEngine) return;
304     pEngine->ProcessReleaseTrigger(this, itEvent);
305     }
306    
307 iliev 2012 void RenderActiveVoices(uint Samples) {
308     RenderVoicesHandler handler(this, Samples);
309 persson 2335 this->ProcessActiveVoices(&handler);
310 iliev 2012
311     SetVoiceCount(handler.VoiceCount);
312     SetDiskStreamCount(handler.StreamCount);
313     }
314    
315 schoenebeck 2879 /**
316     * Called by real-time instrument script functions to schedule a
317     * new note (new note-on event and a new @c Note object linked to it)
318     * @a delay microseconds in future.
319     *
320     * @b IMPORTANT: for the supplied @a delay to be scheduled
321     * correctly, the passed @a pEvent must be assigned a valid
322     * fragment time within the current audio fragment boundaries. That
323     * fragment time will be used by this method as basis for
324     * interpreting what "now" acutally is, and thus it will be used as
325     * basis for calculating the precise scheduling time for @a delay.
326     * The easiest way to achieve this is by copying a recent event
327     * which happened within the current audio fragment cycle: i.e. the
328     * original event which caused calling this method here, or by using
329     * Event::copyTimefrom() method to only copy the time, without any
330     * other event data.
331     *
332     * @param pEvent - note-on event to be scheduled in future (event
333     * data will be copied)
334     * @param delay - amount of microseconds in future (from now) when
335     * event shall be processed
336     * @returns unique note ID of scheduled new note, or NULL on error
337     */
338     note_id_t ScheduleNoteMicroSec(const Event* pEvent, int delay) OVERRIDE {
339     // add (copied) note-on event into scheduler queue
340     const event_id_t noteOnEventID = ScheduleEventMicroSec(pEvent, delay);
341     if (!noteOnEventID) return 0; // error
342     // get access to (copied) event on the scheduler queue
343     RTList<Event>::Iterator itEvent = pEvents->fromID(noteOnEventID);
344     // stick a new note to the (copied) event on the queue
345     const note_id_t noteID = pEngine->LaunchNewNote(this, &*itEvent);
346     return noteID;
347     }
348    
349 schoenebeck 2884 /**
350     * Called by real-time instrument script functions to ignore the note
351     * reflected by given note ID. The note's event will be freed immediately
352     * to its event pool and this will prevent voices to be launched for the
353     * note.
354     *
355     * NOTE: preventing a note by calling this method works only if the note
356     * was launched within the current audio fragment cycle.
357     *
358     * @param id - unique ID of note to be dropped
359     */
360     void IgnoreNote(note_id_t id) OVERRIDE {
361     Pool< Note<V> >* pNotePool =
362     dynamic_cast<NotePool<V>*>(pEngine)->GetNotePool();
363    
364     NoteIterator itNote = pNotePool->fromID(id);
365     if (!itNote) return; // note probably already released
366    
367     // if the note already got active voices, then it is too late to drop it
368     if (!itNote->pActiveVoices->isEmpty()) return;
369    
370     // if the original (note-on) event is not available anymore, then it is too late to drop it
371     RTList<Event>::Iterator itEvent = pEvents->fromID(itNote->eventID);
372     if (!itEvent) return;
373    
374     // drop the note
375     pNotePool->free(itNote);
376    
377     // drop the original event
378     pEvents->free(itEvent);
379     }
380    
381 iliev 2012 RTList<R*>* pRegionsInUse; ///< temporary pointer into the instrument change command, used by the audio thread
382     I* pInstrument;
383    
384     template<class TV, class TRR, class TR, class TD, class TIM, class TI> friend class EngineBase;
385    
386     protected:
387 schoenebeck 2645 EngineChannelBase() :
388     MidiKeyboardManager<V>(this),
389     InstrumentChangeCommandReader(InstrumentChangeCommand)
390     {
391 iliev 2012 pInstrument = NULL;
392    
393     // reset the instrument change command struct (need to be done
394     // twice, as it is double buffered)
395     {
396     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
397     cmd.pRegionsInUse = NULL;
398     cmd.pInstrument = NULL;
399 schoenebeck 2611 cmd.pScript = new InstrumentScript(this);
400 iliev 2012 cmd.bChangeInstrument = false;
401     }
402     {
403     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
404     cmd.pRegionsInUse = NULL;
405     cmd.pInstrument = NULL;
406 schoenebeck 2611 cmd.pScript = new InstrumentScript(this);
407 iliev 2012 cmd.bChangeInstrument = false;
408     }
409     }
410    
411 schoenebeck 2611 virtual ~EngineChannelBase() {
412 schoenebeck 2612 InstrumentScript* previous = NULL; // prevent double free
413 schoenebeck 2611 {
414     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
415 schoenebeck 2612 if (cmd.pScript) {
416     previous = cmd.pScript;
417     delete cmd.pScript;
418     cmd.pScript = NULL;
419     }
420 schoenebeck 2611 }
421     {
422     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
423 schoenebeck 2612 if (cmd.pScript) {
424     if (previous != cmd.pScript)
425     delete cmd.pScript;
426     cmd.pScript = NULL;
427     }
428 schoenebeck 2611 }
429     }
430 iliev 2012
431     typedef typename RTList<V>::Iterator RTListVoiceIterator;
432    
433     class RenderVoicesHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
434     public:
435     uint Samples;
436     uint VoiceCount;
437     uint StreamCount;
438     EngineChannelBase<V, R, I>* pChannel;
439    
440     RenderVoicesHandler(EngineChannelBase<V, R, I>* channel, uint samples) :
441 schoenebeck 3034 Samples(samples), VoiceCount(0), StreamCount(0), pChannel(channel) { }
442 iliev 2012
443     virtual void Process(RTListVoiceIterator& itVoice) {
444     // now render current voice
445     itVoice->Render(Samples);
446     if (itVoice->IsActive()) { // still active
447     if (!itVoice->Orphan) {
448     *(pChannel->pRegionsInUse->allocAppend()) = itVoice->GetRegion();
449     }
450     VoiceCount++;
451    
452     if (itVoice->PlaybackState == Voice::playback_state_disk) {
453     if ((itVoice->DiskStreamRef).State != Stream::state_unused) StreamCount++;
454     }
455     } else { // voice reached end, is now inactive
456 iliev 2244 itVoice->VoiceFreed();
457 iliev 2012 pChannel->FreeVoice(itVoice); // remove voice from the list of active voices
458     }
459     }
460     };
461    
462     typedef typename SynchronizedConfig<InstrumentChangeCmd<R, I> >::Reader SyncConfInstrChangeCmdReader;
463    
464     SynchronizedConfig<InstrumentChangeCmd<R, I> > InstrumentChangeCommand;
465     SyncConfInstrChangeCmdReader InstrumentChangeCommandReader;
466    
467     /** This method is not thread safe! */
468 schoenebeck 2871 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
469     AbstractEngineChannel::ResetInternal(bResetEngine);
470 iliev 2012
471     MidiKeyboardManager<V>::Reset();
472     }
473    
474 schoenebeck 3054 virtual void ResetControllers() OVERRIDE {
475 iliev 2012 AbstractEngineChannel::ResetControllers();
476    
477     MidiKeyboardManager<V>::SustainPedal = false;
478     MidiKeyboardManager<V>::SostenutoPedal = false;
479     }
480    
481     /**
482 schoenebeck 2612 * Unload the currently used and loaded real-time instrument script.
483     * The source code of the script is retained, so that it can still
484     * be reloaded.
485     */
486     void UnloadScriptInUse() {
487     {
488     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
489 schoenebeck 2618 if (cmd.pScript) cmd.pScript->unload();
490 schoenebeck 2612 }
491     {
492     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
493 schoenebeck 2618 if (cmd.pScript) cmd.pScript->unload();
494 schoenebeck 2612 }
495 schoenebeck 2614 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
496 schoenebeck 2612 }
497    
498     /**
499 schoenebeck 2611 * Load real-time instrument script and all its resources required
500     * for the upcoming instrument change.
501     *
502     * @param text - source code of script
503     */
504     void LoadInstrumentScript(const String& text) {
505     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
506     // load the new script
507     cmd.pScript->load(text);
508     }
509    
510     /**
511 iliev 2012 * Changes the instrument for an engine channel.
512     *
513     * @param pInstrument - new instrument
514     * @returns the resulting instrument change command after the
515     * command switch, containing the old instrument and
516     * the dimregions it is using
517     */
518     InstrumentChangeCmd<R, I>& ChangeInstrument(I* pInstrument) {
519     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
520     cmd.pInstrument = pInstrument;
521     cmd.bChangeInstrument = true;
522    
523     return InstrumentChangeCommand.SwitchConfig();
524     }
525    
526     virtual void ProcessKeySwitchChange(int key) = 0;
527     };
528    
529     } // namespace LinuxSampler
530    
531     #endif /* __LS_ENGINECHANNELBASE_H__ */

  ViewVC Help
Powered by ViewVC