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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2871 - (show 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 /***************************************************************************
2 * *
3 * LinuxSampler - modular, streaming capable sampler *
4 * *
5 * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck *
6 * Copyright (C) 2005-2008 Christian Schoenebeck *
7 * Copyright (C) 2009-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 *
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 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 };
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 virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE {
65 return this;
66 }
67
68 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 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
78 cmd.bChangeInstrument = false;
79 }
80 {
81 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
82 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
83 cmd.bChangeInstrument = false;
84 }
85 }
86
87 virtual void ResetRegionsInUse(Pool<R*>* pRegionPool[]) {
88 DeleteRegionsInUse();
89 AllocateRegionsInUse(pRegionPool);
90 }
91
92 virtual void DeleteRegionsInUse() {
93 RTList<R*>* previous = NULL; // prevent double free
94 {
95 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
96 if (cmd.pRegionsInUse) {
97 previous = cmd.pRegionsInUse;
98 delete cmd.pRegionsInUse;
99 cmd.pRegionsInUse = NULL;
100 }
101 cmd.bChangeInstrument = false;
102 }
103 {
104 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
105 if (cmd.pRegionsInUse) {
106 if (cmd.pRegionsInUse != previous)
107 delete cmd.pRegionsInUse;
108 cmd.pRegionsInUse = NULL;
109 }
110 cmd.bChangeInstrument = false;
111 }
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 cmd.bChangeInstrument = false;
119 }
120 {
121 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
122 cmd.pRegionsInUse = new RTList<R*>(pRegionPool[1]);
123 cmd.bChangeInstrument = false;
124 }
125 }
126
127 virtual void Connect(AudioOutputDevice* pAudioOut) {
128 if (pEngine) {
129 if (pEngine->pAudioOutputDevice == pAudioOut) return;
130 DisconnectAudioOutputDevice();
131 }
132 AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut);
133 {
134 LockGuard lock(EngineMutex);
135 pEngine = newEngine;
136 }
137 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 pEvents = new RTList<Event>(pEngine->pEventPool);
139 delayedEvents.pList = new RTList<Event>(pEngine->pEventPool);
140
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 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
191 DeleteRegionsInUse();
192 UnloadScriptInUse();
193
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 if (delayedEvents.pList) {
205 delete delayedEvents.pList;
206 delayedEvents.pList = NULL;
207 }
208
209 MidiKeyboardManager<V>::DeleteActiveVoices();
210 MidiKeyboardManager<V>::DeleteEvents();
211 DeleteGroupEventLists();
212
213 AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice;
214 {
215 LockGuard lock(EngineMutex);
216 pEngine = NULL;
217 }
218 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 /**
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 pEvents->clear();
242 // empty MIDI key specific event lists
243 ClearEventListsHandler handler;
244 this->ProcessActiveVoices(&handler);
245
246 // empty exclusive group specific event lists
247 // (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 }
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 virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) OVERRIDE {
261 dmsg(3,("EngineChannelBase: Received instrument update message.\n"));
262 if (pEngine) pEngine->DisableAndLock();
263 ResetInternal(false/*don't reset engine*/);
264 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 virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) OVERRIDE {
272 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 virtual void OnResourceProgress(float fProgress) OVERRIDE {
284 this->InstrumentStat = int(fProgress * 100.0f);
285 dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat));
286 bStatusChanged = true; // status of engine has changed, so set notify flag
287 }
288
289 void RenderActiveVoices(uint Samples) {
290 RenderVoicesHandler handler(this, Samples);
291 this->ProcessActiveVoices(&handler);
292
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 EngineChannelBase() :
304 MidiKeyboardManager<V>(this),
305 InstrumentChangeCommandReader(InstrumentChangeCommand)
306 {
307 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 cmd.pScript = new InstrumentScript(this);
316 cmd.bChangeInstrument = false;
317 }
318 {
319 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
320 cmd.pRegionsInUse = NULL;
321 cmd.pInstrument = NULL;
322 cmd.pScript = new InstrumentScript(this);
323 cmd.bChangeInstrument = false;
324 }
325 }
326
327 virtual ~EngineChannelBase() {
328 InstrumentScript* previous = NULL; // prevent double free
329 {
330 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
331 if (cmd.pScript) {
332 previous = cmd.pScript;
333 delete cmd.pScript;
334 cmd.pScript = NULL;
335 }
336 }
337 {
338 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
339 if (cmd.pScript) {
340 if (previous != cmd.pScript)
341 delete cmd.pScript;
342 cmd.pScript = NULL;
343 }
344 }
345 }
346
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 itVoice->VoiceFreed();
373 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 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
385 AbstractEngineChannel::ResetInternal(bResetEngine);
386
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 * 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 if (cmd.pScript) cmd.pScript->unload();
406 }
407 {
408 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
409 if (cmd.pScript) cmd.pScript->unload();
410 }
411 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
412 }
413
414 /**
415 * 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 * 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