/[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 2884 - (show 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 /***************************************************************************
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 NotePool {
54 public:
55 virtual Pool<V>* GetVoicePool() = 0;
56 virtual Pool< Note<V> >* GetNotePool() = 0;
57 virtual Pool<note_id_t>* GetNodeIDPool() = 0;
58 };
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 typedef typename RTList< Note<V> >::Iterator NoteIterator;
64 typedef typename RTList<R*>::Iterator RTListRegionIterator;
65 typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;
66
67 virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE {
68 return this;
69 }
70
71 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 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
81 cmd.bChangeInstrument = false;
82 }
83 {
84 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
85 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
86 cmd.bChangeInstrument = false;
87 }
88 }
89
90 virtual void ResetRegionsInUse(Pool<R*>* pRegionPool[]) {
91 DeleteRegionsInUse();
92 AllocateRegionsInUse(pRegionPool);
93 }
94
95 virtual void DeleteRegionsInUse() {
96 RTList<R*>* previous = NULL; // prevent double free
97 {
98 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
99 if (cmd.pRegionsInUse) {
100 previous = cmd.pRegionsInUse;
101 delete cmd.pRegionsInUse;
102 cmd.pRegionsInUse = NULL;
103 }
104 cmd.bChangeInstrument = false;
105 }
106 {
107 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
108 if (cmd.pRegionsInUse) {
109 if (cmd.pRegionsInUse != previous)
110 delete cmd.pRegionsInUse;
111 cmd.pRegionsInUse = NULL;
112 }
113 cmd.bChangeInstrument = false;
114 }
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 cmd.bChangeInstrument = false;
122 }
123 {
124 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
125 cmd.pRegionsInUse = new RTList<R*>(pRegionPool[1]);
126 cmd.bChangeInstrument = false;
127 }
128 }
129
130 virtual void Connect(AudioOutputDevice* pAudioOut) {
131 if (pEngine) {
132 if (pEngine->pAudioOutputDevice == pAudioOut) return;
133 DisconnectAudioOutputDevice();
134 }
135 AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut);
136 {
137 LockGuard lock(EngineMutex);
138 pEngine = newEngine;
139 }
140 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 pEvents = new RTList<Event>(pEngine->pEventPool);
142 delayedEvents.pList = new RTList<Event>(pEngine->pEventPool);
143
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 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
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 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
197 DeleteRegionsInUse();
198 UnloadScriptInUse();
199
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 if (delayedEvents.pList) {
211 delete delayedEvents.pList;
212 delayedEvents.pList = NULL;
213 }
214
215 MidiKeyboardManager<V>::DeleteActiveNotesLists();
216 MidiKeyboardManager<V>::DeleteEventsLists();
217 DeleteGroupEventLists();
218
219 AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice;
220 {
221 LockGuard lock(EngineMutex);
222 pEngine = NULL;
223 }
224 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 /**
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 pEvents->clear();
248 // empty MIDI key specific event lists
249 ClearEventListsHandler handler;
250 this->ProcessActiveVoices(&handler);
251
252 // empty exclusive group specific event lists
253 // (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 }
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 virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) OVERRIDE {
267 dmsg(3,("EngineChannelBase: Received instrument update message.\n"));
268 if (pEngine) pEngine->DisableAndLock();
269 ResetInternal(false/*don't reset engine*/);
270 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 virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) OVERRIDE {
278 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 virtual void OnResourceProgress(float fProgress) OVERRIDE {
290 this->InstrumentStat = int(fProgress * 100.0f);
291 dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat));
292 bStatusChanged = true; // status of engine has changed, so set notify flag
293 }
294
295 void RenderActiveVoices(uint Samples) {
296 RenderVoicesHandler handler(this, Samples);
297 this->ProcessActiveVoices(&handler);
298
299 SetVoiceCount(handler.VoiceCount);
300 SetDiskStreamCount(handler.StreamCount);
301 }
302
303 /**
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 /**
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 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 EngineChannelBase() :
376 MidiKeyboardManager<V>(this),
377 InstrumentChangeCommandReader(InstrumentChangeCommand)
378 {
379 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 cmd.pScript = new InstrumentScript(this);
388 cmd.bChangeInstrument = false;
389 }
390 {
391 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
392 cmd.pRegionsInUse = NULL;
393 cmd.pInstrument = NULL;
394 cmd.pScript = new InstrumentScript(this);
395 cmd.bChangeInstrument = false;
396 }
397 }
398
399 virtual ~EngineChannelBase() {
400 InstrumentScript* previous = NULL; // prevent double free
401 {
402 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
403 if (cmd.pScript) {
404 previous = cmd.pScript;
405 delete cmd.pScript;
406 cmd.pScript = NULL;
407 }
408 }
409 {
410 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
411 if (cmd.pScript) {
412 if (previous != cmd.pScript)
413 delete cmd.pScript;
414 cmd.pScript = NULL;
415 }
416 }
417 }
418
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 itVoice->VoiceFreed();
445 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 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
457 AbstractEngineChannel::ResetInternal(bResetEngine);
458
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 * 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 if (cmd.pScript) cmd.pScript->unload();
478 }
479 {
480 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
481 if (cmd.pScript) cmd.pScript->unload();
482 }
483 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
484 }
485
486 /**
487 * 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 * 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