/[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 2884 - (hide annotations) (download) (as text)
Wed Apr 20 15:22:58 2016 UTC (8 years ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 24890 byte(s)
* Automake: set environment variable GCC_COLORS=auto to allow GCC to
  auto detect whether it (sh/c)ould output its messages in color.
* Fixed behavior of built-in script function "ignore_event()".
* Bumped version (2.0.0.svn4).

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     virtual void Connect(AudioOutputDevice* pAudioOut) {
131     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     virtual void DisconnectAudioOutputDevice() {
193     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     void RenderActiveVoices(uint Samples) {
296     RenderVoicesHandler handler(this, Samples);
297 persson 2335 this->ProcessActiveVoices(&handler);
298 iliev 2012
299     SetVoiceCount(handler.VoiceCount);
300     SetDiskStreamCount(handler.StreamCount);
301     }
302    
303 schoenebeck 2879 /**
304     * Called by real-time instrument script functions to schedule a
305     * new note (new note-on event and a new @c Note object linked to it)
306     * @a delay microseconds in future.
307     *
308     * @b IMPORTANT: for the supplied @a delay to be scheduled
309     * correctly, the passed @a pEvent must be assigned a valid
310     * fragment time within the current audio fragment boundaries. That
311     * fragment time will be used by this method as basis for
312     * interpreting what "now" acutally is, and thus it will be used as
313     * basis for calculating the precise scheduling time for @a delay.
314     * The easiest way to achieve this is by copying a recent event
315     * which happened within the current audio fragment cycle: i.e. the
316     * original event which caused calling this method here, or by using
317     * Event::copyTimefrom() method to only copy the time, without any
318     * other event data.
319     *
320     * @param pEvent - note-on event to be scheduled in future (event
321     * data will be copied)
322     * @param delay - amount of microseconds in future (from now) when
323     * event shall be processed
324     * @returns unique note ID of scheduled new note, or NULL on error
325     */
326     note_id_t ScheduleNoteMicroSec(const Event* pEvent, int delay) OVERRIDE {
327     // add (copied) note-on event into scheduler queue
328     const event_id_t noteOnEventID = ScheduleEventMicroSec(pEvent, delay);
329     if (!noteOnEventID) return 0; // error
330     // get access to (copied) event on the scheduler queue
331     RTList<Event>::Iterator itEvent = pEvents->fromID(noteOnEventID);
332     // stick a new note to the (copied) event on the queue
333     const note_id_t noteID = pEngine->LaunchNewNote(this, &*itEvent);
334     return noteID;
335     }
336    
337 schoenebeck 2884 /**
338     * Called by real-time instrument script functions to ignore the note
339     * reflected by given note ID. The note's event will be freed immediately
340     * to its event pool and this will prevent voices to be launched for the
341     * note.
342     *
343     * NOTE: preventing a note by calling this method works only if the note
344     * was launched within the current audio fragment cycle.
345     *
346     * @param id - unique ID of note to be dropped
347     */
348     void IgnoreNote(note_id_t id) OVERRIDE {
349     Pool< Note<V> >* pNotePool =
350     dynamic_cast<NotePool<V>*>(pEngine)->GetNotePool();
351    
352     NoteIterator itNote = pNotePool->fromID(id);
353     if (!itNote) return; // note probably already released
354    
355     // if the note already got active voices, then it is too late to drop it
356     if (!itNote->pActiveVoices->isEmpty()) return;
357    
358     // if the original (note-on) event is not available anymore, then it is too late to drop it
359     RTList<Event>::Iterator itEvent = pEvents->fromID(itNote->eventID);
360     if (!itEvent) return;
361    
362     // drop the note
363     pNotePool->free(itNote);
364    
365     // drop the original event
366     pEvents->free(itEvent);
367     }
368    
369 iliev 2012 RTList<R*>* pRegionsInUse; ///< temporary pointer into the instrument change command, used by the audio thread
370     I* pInstrument;
371    
372     template<class TV, class TRR, class TR, class TD, class TIM, class TI> friend class EngineBase;
373    
374     protected:
375 schoenebeck 2645 EngineChannelBase() :
376     MidiKeyboardManager<V>(this),
377     InstrumentChangeCommandReader(InstrumentChangeCommand)
378     {
379 iliev 2012 pInstrument = NULL;
380    
381     // reset the instrument change command struct (need to be done
382     // twice, as it is double buffered)
383     {
384     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
385     cmd.pRegionsInUse = NULL;
386     cmd.pInstrument = NULL;
387 schoenebeck 2611 cmd.pScript = new InstrumentScript(this);
388 iliev 2012 cmd.bChangeInstrument = false;
389     }
390     {
391     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
392     cmd.pRegionsInUse = NULL;
393     cmd.pInstrument = NULL;
394 schoenebeck 2611 cmd.pScript = new InstrumentScript(this);
395 iliev 2012 cmd.bChangeInstrument = false;
396     }
397     }
398    
399 schoenebeck 2611 virtual ~EngineChannelBase() {
400 schoenebeck 2612 InstrumentScript* previous = NULL; // prevent double free
401 schoenebeck 2611 {
402     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
403 schoenebeck 2612 if (cmd.pScript) {
404     previous = cmd.pScript;
405     delete cmd.pScript;
406     cmd.pScript = NULL;
407     }
408 schoenebeck 2611 }
409     {
410     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
411 schoenebeck 2612 if (cmd.pScript) {
412     if (previous != cmd.pScript)
413     delete cmd.pScript;
414     cmd.pScript = NULL;
415     }
416 schoenebeck 2611 }
417     }
418 iliev 2012
419     typedef typename RTList<V>::Iterator RTListVoiceIterator;
420    
421     class RenderVoicesHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
422     public:
423     uint Samples;
424     uint VoiceCount;
425     uint StreamCount;
426     EngineChannelBase<V, R, I>* pChannel;
427    
428     RenderVoicesHandler(EngineChannelBase<V, R, I>* channel, uint samples) :
429     pChannel(channel), Samples(samples), VoiceCount(0), StreamCount(0) { }
430    
431     virtual void Process(RTListVoiceIterator& itVoice) {
432     // now render current voice
433     itVoice->Render(Samples);
434     if (itVoice->IsActive()) { // still active
435     if (!itVoice->Orphan) {
436     *(pChannel->pRegionsInUse->allocAppend()) = itVoice->GetRegion();
437     }
438     VoiceCount++;
439    
440     if (itVoice->PlaybackState == Voice::playback_state_disk) {
441     if ((itVoice->DiskStreamRef).State != Stream::state_unused) StreamCount++;
442     }
443     } else { // voice reached end, is now inactive
444 iliev 2244 itVoice->VoiceFreed();
445 iliev 2012 pChannel->FreeVoice(itVoice); // remove voice from the list of active voices
446     }
447     }
448     };
449    
450     typedef typename SynchronizedConfig<InstrumentChangeCmd<R, I> >::Reader SyncConfInstrChangeCmdReader;
451    
452     SynchronizedConfig<InstrumentChangeCmd<R, I> > InstrumentChangeCommand;
453     SyncConfInstrChangeCmdReader InstrumentChangeCommandReader;
454    
455     /** This method is not thread safe! */
456 schoenebeck 2871 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
457     AbstractEngineChannel::ResetInternal(bResetEngine);
458 iliev 2012
459     MidiKeyboardManager<V>::Reset();
460     }
461    
462     virtual void ResetControllers() {
463     AbstractEngineChannel::ResetControllers();
464    
465     MidiKeyboardManager<V>::SustainPedal = false;
466     MidiKeyboardManager<V>::SostenutoPedal = false;
467     }
468    
469     /**
470 schoenebeck 2612 * Unload the currently used and loaded real-time instrument script.
471     * The source code of the script is retained, so that it can still
472     * be reloaded.
473     */
474     void UnloadScriptInUse() {
475     {
476     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
477 schoenebeck 2618 if (cmd.pScript) cmd.pScript->unload();
478 schoenebeck 2612 }
479     {
480     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
481 schoenebeck 2618 if (cmd.pScript) cmd.pScript->unload();
482 schoenebeck 2612 }
483 schoenebeck 2614 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
484 schoenebeck 2612 }
485    
486     /**
487 schoenebeck 2611 * Load real-time instrument script and all its resources required
488     * for the upcoming instrument change.
489     *
490     * @param text - source code of script
491     */
492     void LoadInstrumentScript(const String& text) {
493     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
494     // load the new script
495     cmd.pScript->load(text);
496     }
497    
498     /**
499 iliev 2012 * Changes the instrument for an engine channel.
500     *
501     * @param pInstrument - new instrument
502     * @returns the resulting instrument change command after the
503     * command switch, containing the old instrument and
504     * the dimregions it is using
505     */
506     InstrumentChangeCmd<R, I>& ChangeInstrument(I* pInstrument) {
507     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
508     cmd.pInstrument = pInstrument;
509     cmd.bChangeInstrument = true;
510    
511     return InstrumentChangeCommand.SwitchConfig();
512     }
513    
514     virtual void ProcessKeySwitchChange(int key) = 0;
515     };
516    
517     } // namespace LinuxSampler
518    
519     #endif /* __LS_ENGINECHANNELBASE_H__ */

  ViewVC Help
Powered by ViewVC