/[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 2871 - (hide annotations) (download) (as text)
Sun Apr 10 18:22:23 2016 UTC (8 years ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 21137 byte(s)
* All engines: Implemented scheduler for delayed MIDI events and for
  suspended real-time instrument scripts.
* Real-Time instrument scripts: Implemented support for built-in "wait()"
  function's "duration-us" argument, thus scripts using this function are
  now correctly resumed after the requested amount of microseconds.
* Real-Time instrument scripts: Implemented support for built-in
  "play_note()" function's "duration-us" argument, thus notes triggered
  with this argument are now correctly released after the requested amount
  of microseconds.
* Real-Time instrument scripts: Fixed crash which happened when trying to
  reference an undeclared script variable.
* Real-Time instrument scripts: Script events were not cleared when
  engine channel was reset, potentially causing undefined behavior.
* All engines: Attempt to partly fix resetting engine channels vs.
  resetting engine, an overall cleanup of the Reset*(),
  ConnectAudioDevice(), DisconnectAudioDevice() API methods would still be
  desirable though, because the current situation is still inconsistent
  and error prone.
* Bumped version (2.0.0.svn2).

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     class VoicePool {
54     public:
55     virtual Pool<V>* GetVoicePool() = 0;
56     };
57    
58     template <class V /* Voice */, class R /* Region */, class I /* Instrument */>
59     class EngineChannelBase: public AbstractEngineChannel, public MidiKeyboardManager<V>, public ResourceConsumer<I> {
60     public:
61     typedef typename RTList<R*>::Iterator RTListRegionIterator;
62     typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;
63    
64 schoenebeck 2613 virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE {
65     return this;
66     }
67    
68 iliev 2012 virtual void HandBack(I* Instrument) {
69     ResourceManager<InstrumentManager::instrument_id_t, I>* mgr =
70     dynamic_cast<ResourceManager<InstrumentManager::instrument_id_t, I>*>(pEngine->GetInstrumentManager());
71     mgr->HandBack(Instrument, this);
72     }
73    
74     virtual void ClearRegionsInUse() {
75     {
76     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
77 persson 2165 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
78     cmd.bChangeInstrument = false;
79 iliev 2012 }
80     {
81     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
82 persson 2165 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
83     cmd.bChangeInstrument = false;
84 iliev 2012 }
85     }
86    
87     virtual void ResetRegionsInUse(Pool<R*>* pRegionPool[]) {
88     DeleteRegionsInUse();
89     AllocateRegionsInUse(pRegionPool);
90     }
91    
92     virtual void DeleteRegionsInUse() {
93 schoenebeck 2612 RTList<R*>* previous = NULL; // prevent double free
94 iliev 2012 {
95     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
96 persson 2165 if (cmd.pRegionsInUse) {
97 schoenebeck 2612 previous = cmd.pRegionsInUse;
98 iliev 2012 delete cmd.pRegionsInUse;
99     cmd.pRegionsInUse = NULL;
100     }
101 persson 2165 cmd.bChangeInstrument = false;
102 iliev 2012 }
103     {
104     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
105 persson 2165 if (cmd.pRegionsInUse) {
106 schoenebeck 2612 if (cmd.pRegionsInUse != previous)
107     delete cmd.pRegionsInUse;
108 iliev 2012 cmd.pRegionsInUse = NULL;
109     }
110 persson 2165 cmd.bChangeInstrument = false;
111 iliev 2012 }
112     }
113    
114     virtual void AllocateRegionsInUse(Pool<R*>* pRegionPool[]) {
115     {
116     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
117     cmd.pRegionsInUse = new RTList<R*>(pRegionPool[0]);
118 persson 2165 cmd.bChangeInstrument = false;
119 iliev 2012 }
120     {
121     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
122     cmd.pRegionsInUse = new RTList<R*>(pRegionPool[1]);
123 persson 2165 cmd.bChangeInstrument = false;
124 iliev 2012 }
125     }
126    
127     virtual void Connect(AudioOutputDevice* pAudioOut) {
128     if (pEngine) {
129     if (pEngine->pAudioOutputDevice == pAudioOut) return;
130     DisconnectAudioOutputDevice();
131     }
132 persson 2326 AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut);
133 persson 2427 {
134     LockGuard lock(EngineMutex);
135     pEngine = newEngine;
136     }
137 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
138 iliev 2012 pEvents = new RTList<Event>(pEngine->pEventPool);
139 schoenebeck 2871 delayedEvents.pList = new RTList<Event>(pEngine->pEventPool);
140 iliev 2012
141     RegionPools<R>* pRegionPool = dynamic_cast<RegionPools<R>*>(pEngine);
142     // reset the instrument change command struct (need to be done
143     // twice, as it is double buffered)
144     {
145     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
146     cmd.pRegionsInUse = new RTList<R*>(pRegionPool->GetRegionPool(0));
147     cmd.pInstrument = 0;
148     cmd.bChangeInstrument = false;
149     }
150     {
151     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
152     cmd.pRegionsInUse = new RTList<R*>(pRegionPool->GetRegionPool(1));
153     cmd.pInstrument = 0;
154     cmd.bChangeInstrument = false;
155     }
156    
157     if (pInstrument != NULL) {
158     pInstrument = NULL;
159     InstrumentStat = -1;
160     InstrumentIdx = -1;
161     InstrumentIdxName = "";
162     InstrumentFile = "";
163     bStatusChanged = true;
164     }
165    
166     VoicePool<V>* pVoicePool = dynamic_cast<VoicePool<V>*>(pEngine);
167     MidiKeyboardManager<V>::AllocateActiveVoices(pVoicePool->GetVoicePool());
168     MidiKeyboardManager<V>::AllocateEvents(pEngine->pEventPool);
169    
170     AudioDeviceChannelLeft = 0;
171     AudioDeviceChannelRight = 1;
172     if (fxSends.empty()) { // render directly into the AudioDevice's output buffers
173     pChannelLeft = pAudioOut->Channel(AudioDeviceChannelLeft);
174     pChannelRight = pAudioOut->Channel(AudioDeviceChannelRight);
175     } else { // use local buffers for rendering and copy later
176     // ensure the local buffers have the correct size
177     if (pChannelLeft) delete pChannelLeft;
178     if (pChannelRight) delete pChannelRight;
179     pChannelLeft = new AudioChannel(0, pAudioOut->MaxSamplesPerCycle());
180     pChannelRight = new AudioChannel(1, pAudioOut->MaxSamplesPerCycle());
181     }
182     if (pEngine->EngineDisabled.GetUnsafe()) pEngine->Enable();
183     MidiInputPort::AddSysexListener(pEngine);
184     }
185    
186     virtual void DisconnectAudioOutputDevice() {
187     if (pEngine) { // if clause to prevent disconnect loops
188    
189 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
190 iliev 2012
191     DeleteRegionsInUse();
192 schoenebeck 2612 UnloadScriptInUse();
193 iliev 2012
194     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
195     if (cmd.pInstrument) {
196     // release the currently loaded instrument
197     HandBack(cmd.pInstrument);
198     }
199    
200     if (pEvents) {
201     delete pEvents;
202     pEvents = NULL;
203     }
204 schoenebeck 2871 if (delayedEvents.pList) {
205     delete delayedEvents.pList;
206     delayedEvents.pList = NULL;
207     }
208 iliev 2012
209     MidiKeyboardManager<V>::DeleteActiveVoices();
210     MidiKeyboardManager<V>::DeleteEvents();
211 persson 2114 DeleteGroupEventLists();
212 iliev 2012
213     AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice;
214 persson 2427 {
215     LockGuard lock(EngineMutex);
216     pEngine = NULL;
217     }
218 iliev 2012 AbstractEngine::FreeEngine(this, oldAudioDevice);
219     AudioDeviceChannelLeft = -1;
220     AudioDeviceChannelRight = -1;
221     if (!fxSends.empty()) { // free the local rendering buffers
222     if (pChannelLeft) delete pChannelLeft;
223     if (pChannelRight) delete pChannelRight;
224     }
225     pChannelLeft = NULL;
226     pChannelRight = NULL;
227     }
228     }
229    
230     class ClearEventListsHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
231     public:
232     virtual bool Process(MidiKey* pMidiKey) { pMidiKey->pEvents->clear(); return false; }
233     };
234    
235 schoenebeck 2871 /**
236     * Free all events of the current audio fragment cycle. Calling
237     * this method will @b NOT free events scheduled past the current
238     * fragment's boundary! (@see AbstractEngineChannel::delayedEvents).
239     */
240     void ClearEventListsOfCurrentFragment() {
241 iliev 2012 pEvents->clear();
242     // empty MIDI key specific event lists
243     ClearEventListsHandler handler;
244 persson 2335 this->ProcessActiveVoices(&handler);
245 persson 2114
246     // empty exclusive group specific event lists
247 persson 2352 // (pInstrument == 0 could mean that LoadInstrument is
248     // building new group event lists, so we must check
249     // for that)
250     if (pInstrument) ClearGroupEventLists();
251 iliev 2012 }
252    
253     // implementation of abstract methods derived from interface class 'InstrumentConsumer'
254    
255     /**
256     * Will be called by the InstrumentResourceManager when the instrument
257     * we are currently using on this EngineChannel is going to be updated,
258     * so we can stop playback before that happens.
259     */
260 schoenebeck 2434 virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) OVERRIDE {
261 iliev 2012 dmsg(3,("EngineChannelBase: Received instrument update message.\n"));
262     if (pEngine) pEngine->DisableAndLock();
263 schoenebeck 2871 ResetInternal(false/*don't reset engine*/);
264 iliev 2012 this->pInstrument = NULL;
265     }
266    
267     /**
268     * Will be called by the InstrumentResourceManager when the instrument
269     * update process was completed, so we can continue with playback.
270     */
271 schoenebeck 2434 virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) OVERRIDE {
272 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())
273     if (pEngine) pEngine->Enable();
274     bStatusChanged = true; // status of engine has changed, so set notify flag
275     }
276    
277     /**
278     * Will be called by the InstrumentResourceManager on progress changes
279     * while loading or realoading an instrument for this EngineChannel.
280     *
281     * @param fProgress - current progress as value between 0.0 and 1.0
282     */
283 schoenebeck 2434 virtual void OnResourceProgress(float fProgress) OVERRIDE {
284 iliev 2012 this->InstrumentStat = int(fProgress * 100.0f);
285 persson 2837 dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat));
286 iliev 2012 bStatusChanged = true; // status of engine has changed, so set notify flag
287     }
288    
289     void RenderActiveVoices(uint Samples) {
290     RenderVoicesHandler handler(this, Samples);
291 persson 2335 this->ProcessActiveVoices(&handler);
292 iliev 2012
293     SetVoiceCount(handler.VoiceCount);
294     SetDiskStreamCount(handler.StreamCount);
295     }
296    
297     RTList<R*>* pRegionsInUse; ///< temporary pointer into the instrument change command, used by the audio thread
298     I* pInstrument;
299    
300     template<class TV, class TRR, class TR, class TD, class TIM, class TI> friend class EngineBase;
301    
302     protected:
303 schoenebeck 2645 EngineChannelBase() :
304     MidiKeyboardManager<V>(this),
305     InstrumentChangeCommandReader(InstrumentChangeCommand)
306     {
307 iliev 2012 pInstrument = NULL;
308    
309     // reset the instrument change command struct (need to be done
310     // twice, as it is double buffered)
311     {
312     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
313     cmd.pRegionsInUse = NULL;
314     cmd.pInstrument = NULL;
315 schoenebeck 2611 cmd.pScript = new InstrumentScript(this);
316 iliev 2012 cmd.bChangeInstrument = false;
317     }
318     {
319     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
320     cmd.pRegionsInUse = NULL;
321     cmd.pInstrument = NULL;
322 schoenebeck 2611 cmd.pScript = new InstrumentScript(this);
323 iliev 2012 cmd.bChangeInstrument = false;
324     }
325     }
326    
327 schoenebeck 2611 virtual ~EngineChannelBase() {
328 schoenebeck 2612 InstrumentScript* previous = NULL; // prevent double free
329 schoenebeck 2611 {
330     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
331 schoenebeck 2612 if (cmd.pScript) {
332     previous = cmd.pScript;
333     delete cmd.pScript;
334     cmd.pScript = NULL;
335     }
336 schoenebeck 2611 }
337     {
338     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
339 schoenebeck 2612 if (cmd.pScript) {
340     if (previous != cmd.pScript)
341     delete cmd.pScript;
342     cmd.pScript = NULL;
343     }
344 schoenebeck 2611 }
345     }
346 iliev 2012
347     typedef typename RTList<V>::Iterator RTListVoiceIterator;
348    
349     class RenderVoicesHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
350     public:
351     uint Samples;
352     uint VoiceCount;
353     uint StreamCount;
354     EngineChannelBase<V, R, I>* pChannel;
355    
356     RenderVoicesHandler(EngineChannelBase<V, R, I>* channel, uint samples) :
357     pChannel(channel), Samples(samples), VoiceCount(0), StreamCount(0) { }
358    
359     virtual void Process(RTListVoiceIterator& itVoice) {
360     // now render current voice
361     itVoice->Render(Samples);
362     if (itVoice->IsActive()) { // still active
363     if (!itVoice->Orphan) {
364     *(pChannel->pRegionsInUse->allocAppend()) = itVoice->GetRegion();
365     }
366     VoiceCount++;
367    
368     if (itVoice->PlaybackState == Voice::playback_state_disk) {
369     if ((itVoice->DiskStreamRef).State != Stream::state_unused) StreamCount++;
370     }
371     } else { // voice reached end, is now inactive
372 iliev 2244 itVoice->VoiceFreed();
373 iliev 2012 pChannel->FreeVoice(itVoice); // remove voice from the list of active voices
374     }
375     }
376     };
377    
378     typedef typename SynchronizedConfig<InstrumentChangeCmd<R, I> >::Reader SyncConfInstrChangeCmdReader;
379    
380     SynchronizedConfig<InstrumentChangeCmd<R, I> > InstrumentChangeCommand;
381     SyncConfInstrChangeCmdReader InstrumentChangeCommandReader;
382    
383     /** This method is not thread safe! */
384 schoenebeck 2871 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
385     AbstractEngineChannel::ResetInternal(bResetEngine);
386 iliev 2012
387     MidiKeyboardManager<V>::Reset();
388     }
389    
390     virtual void ResetControllers() {
391     AbstractEngineChannel::ResetControllers();
392    
393     MidiKeyboardManager<V>::SustainPedal = false;
394     MidiKeyboardManager<V>::SostenutoPedal = false;
395     }
396    
397     /**
398 schoenebeck 2612 * Unload the currently used and loaded real-time instrument script.
399     * The source code of the script is retained, so that it can still
400     * be reloaded.
401     */
402     void UnloadScriptInUse() {
403     {
404     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
405 schoenebeck 2618 if (cmd.pScript) cmd.pScript->unload();
406 schoenebeck 2612 }
407     {
408     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
409 schoenebeck 2618 if (cmd.pScript) cmd.pScript->unload();
410 schoenebeck 2612 }
411 schoenebeck 2614 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
412 schoenebeck 2612 }
413    
414     /**
415 schoenebeck 2611 * Load real-time instrument script and all its resources required
416     * for the upcoming instrument change.
417     *
418     * @param text - source code of script
419     */
420     void LoadInstrumentScript(const String& text) {
421     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
422     // load the new script
423     cmd.pScript->load(text);
424     }
425    
426     /**
427 iliev 2012 * Changes the instrument for an engine channel.
428     *
429     * @param pInstrument - new instrument
430     * @returns the resulting instrument change command after the
431     * command switch, containing the old instrument and
432     * the dimregions it is using
433     */
434     InstrumentChangeCmd<R, I>& ChangeInstrument(I* pInstrument) {
435     InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
436     cmd.pInstrument = pInstrument;
437     cmd.bChangeInstrument = true;
438    
439     return InstrumentChangeCommand.SwitchConfig();
440     }
441    
442     virtual void ProcessKeySwitchChange(int key) = 0;
443     };
444    
445     } // namespace LinuxSampler
446    
447     #endif /* __LS_ENGINECHANNELBASE_H__ */

  ViewVC Help
Powered by ViewVC