/[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 3054 - (show annotations) (download) (as text)
Thu Dec 15 12:47:45 2016 UTC (7 years, 4 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 /***************************************************************************
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) OVERRIDE {
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() OVERRIDE {
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 /**
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 void RenderActiveVoices(uint Samples) {
308 RenderVoicesHandler handler(this, Samples);
309 this->ProcessActiveVoices(&handler);
310
311 SetVoiceCount(handler.VoiceCount);
312 SetDiskStreamCount(handler.StreamCount);
313 }
314
315 /**
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 /**
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 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 EngineChannelBase() :
388 MidiKeyboardManager<V>(this),
389 InstrumentChangeCommandReader(InstrumentChangeCommand)
390 {
391 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 cmd.pScript = new InstrumentScript(this);
400 cmd.bChangeInstrument = false;
401 }
402 {
403 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
404 cmd.pRegionsInUse = NULL;
405 cmd.pInstrument = NULL;
406 cmd.pScript = new InstrumentScript(this);
407 cmd.bChangeInstrument = false;
408 }
409 }
410
411 virtual ~EngineChannelBase() {
412 InstrumentScript* previous = NULL; // prevent double free
413 {
414 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
415 if (cmd.pScript) {
416 previous = cmd.pScript;
417 delete cmd.pScript;
418 cmd.pScript = NULL;
419 }
420 }
421 {
422 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
423 if (cmd.pScript) {
424 if (previous != cmd.pScript)
425 delete cmd.pScript;
426 cmd.pScript = NULL;
427 }
428 }
429 }
430
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 Samples(samples), VoiceCount(0), StreamCount(0), pChannel(channel) { }
442
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 itVoice->VoiceFreed();
457 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 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
469 AbstractEngineChannel::ResetInternal(bResetEngine);
470
471 MidiKeyboardManager<V>::Reset();
472 }
473
474 virtual void ResetControllers() OVERRIDE {
475 AbstractEngineChannel::ResetControllers();
476
477 MidiKeyboardManager<V>::SustainPedal = false;
478 MidiKeyboardManager<V>::SostenutoPedal = false;
479 }
480
481 /**
482 * 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 if (cmd.pScript) cmd.pScript->unload();
490 }
491 {
492 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
493 if (cmd.pScript) cmd.pScript->unload();
494 }
495 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
496 }
497
498 /**
499 * 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 * 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