/[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 3205 - (show annotations) (download) (as text)
Wed May 24 20:05:38 2017 UTC (6 years, 10 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 27453 byte(s)
* Fixed invalid (note-on) event ID being assigned to new Note
  objects.
* Fixed a bunch of scheduler time related bugs.
* Bumped version (2.0.0.svn47).

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-2017 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 /**
56 * Pool from where Voice objects are allocated from (and freed back to).
57 */
58 virtual Pool<V>* GetVoicePool() = 0;
59
60 /**
61 * Pool from where new Note objects are allocated from (and freed back to).
62 */
63 virtual Pool< Note<V> >* GetNotePool() = 0;
64
65 /**
66 * Pool for saving already existing Note object IDs somewhere.
67 *
68 * @b IMPORTANT: This pool is @b NOT used for generating any IDs for
69 * Note objects! The respective Note objective IDs are emitted by
70 * the Note object pool (see GetNotePool() above).
71 */
72 virtual Pool<note_id_t>* GetNoteIDPool() = 0;
73 };
74
75 template <class V /* Voice */, class R /* Region */, class I /* Instrument */>
76 class EngineChannelBase: public AbstractEngineChannel, public MidiKeyboardManager<V>, public ResourceConsumer<I> {
77 public:
78 typedef typename RTList< Note<V> >::Iterator NoteIterator;
79 typedef typename RTList<R*>::Iterator RTListRegionIterator;
80 typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;
81
82 virtual MidiKeyboardManagerBase* GetMidiKeyboardManager() OVERRIDE {
83 return this;
84 }
85
86 virtual void HandBack(I* Instrument) {
87 ResourceManager<InstrumentManager::instrument_id_t, I>* mgr =
88 dynamic_cast<ResourceManager<InstrumentManager::instrument_id_t, I>*>(pEngine->GetInstrumentManager());
89 mgr->HandBack(Instrument, this);
90 }
91
92 virtual void ClearRegionsInUse() {
93 {
94 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
95 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
96 cmd.bChangeInstrument = false;
97 }
98 {
99 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
100 if (cmd.pRegionsInUse) cmd.pRegionsInUse->clear();
101 cmd.bChangeInstrument = false;
102 }
103 }
104
105 virtual void ResetRegionsInUse(Pool<R*>* pRegionPool[]) {
106 DeleteRegionsInUse();
107 AllocateRegionsInUse(pRegionPool);
108 }
109
110 virtual void DeleteRegionsInUse() {
111 RTList<R*>* previous = NULL; // prevent double free
112 {
113 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
114 if (cmd.pRegionsInUse) {
115 previous = cmd.pRegionsInUse;
116 delete cmd.pRegionsInUse;
117 cmd.pRegionsInUse = NULL;
118 }
119 cmd.bChangeInstrument = false;
120 }
121 {
122 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
123 if (cmd.pRegionsInUse) {
124 if (cmd.pRegionsInUse != previous)
125 delete cmd.pRegionsInUse;
126 cmd.pRegionsInUse = NULL;
127 }
128 cmd.bChangeInstrument = false;
129 }
130 }
131
132 virtual void AllocateRegionsInUse(Pool<R*>* pRegionPool[]) {
133 {
134 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
135 cmd.pRegionsInUse = new RTList<R*>(pRegionPool[0]);
136 cmd.bChangeInstrument = false;
137 }
138 {
139 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
140 cmd.pRegionsInUse = new RTList<R*>(pRegionPool[1]);
141 cmd.bChangeInstrument = false;
142 }
143 }
144
145 virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE {
146 if (pEngine) {
147 if (pEngine->pAudioOutputDevice == pAudioOut) return;
148 DisconnectAudioOutputDevice();
149 }
150 AbstractEngine* newEngine = AbstractEngine::AcquireEngine(this, pAudioOut);
151 {
152 LockGuard lock(EngineMutex);
153 pEngine = newEngine;
154 }
155 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
156 pEvents = new RTList<Event>(pEngine->pEventPool);
157 delayedEvents.pList = new RTList<Event>(pEngine->pEventPool);
158
159 RegionPools<R>* pRegionPool = dynamic_cast<RegionPools<R>*>(pEngine);
160 // reset the instrument change command struct (need to be done
161 // twice, as it is double buffered)
162 {
163 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
164 cmd.pRegionsInUse = new RTList<R*>(pRegionPool->GetRegionPool(0));
165 cmd.pInstrument = 0;
166 cmd.bChangeInstrument = false;
167 }
168 {
169 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
170 cmd.pRegionsInUse = new RTList<R*>(pRegionPool->GetRegionPool(1));
171 cmd.pInstrument = 0;
172 cmd.bChangeInstrument = false;
173 }
174
175 if (pInstrument != NULL) {
176 pInstrument = NULL;
177 InstrumentStat = -1;
178 InstrumentIdx = -1;
179 InstrumentIdxName = "";
180 InstrumentFile = "";
181 bStatusChanged = true;
182 }
183
184 NotePool<V>* pNotePool = dynamic_cast<NotePool<V>*>(pEngine);
185 MidiKeyboardManager<V>::AllocateActiveNotesLists(
186 pNotePool->GetNotePool(),
187 pNotePool->GetVoicePool()
188 );
189 MidiKeyboardManager<V>::AllocateEventsLists(pEngine->pEventPool);
190
191 AudioDeviceChannelLeft = 0;
192 AudioDeviceChannelRight = 1;
193 if (fxSends.empty()) { // render directly into the AudioDevice's output buffers
194 pChannelLeft = pAudioOut->Channel(AudioDeviceChannelLeft);
195 pChannelRight = pAudioOut->Channel(AudioDeviceChannelRight);
196 } else { // use local buffers for rendering and copy later
197 // ensure the local buffers have the correct size
198 if (pChannelLeft) delete pChannelLeft;
199 if (pChannelRight) delete pChannelRight;
200 pChannelLeft = new AudioChannel(0, pAudioOut->MaxSamplesPerCycle());
201 pChannelRight = new AudioChannel(1, pAudioOut->MaxSamplesPerCycle());
202 }
203 if (pEngine->EngineDisabled.GetUnsafe()) pEngine->Enable();
204 MidiInputPort::AddSysexListener(pEngine);
205 }
206
207 virtual void DisconnectAudioOutputDevice() OVERRIDE {
208 if (pEngine) { // if clause to prevent disconnect loops
209
210 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
211
212 DeleteRegionsInUse();
213 UnloadScriptInUse();
214
215 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
216 if (cmd.pInstrument) {
217 // release the currently loaded instrument
218 HandBack(cmd.pInstrument);
219 }
220
221 if (pEvents) {
222 delete pEvents;
223 pEvents = NULL;
224 }
225 if (delayedEvents.pList) {
226 delete delayedEvents.pList;
227 delayedEvents.pList = NULL;
228 }
229
230 MidiKeyboardManager<V>::DeleteActiveNotesLists();
231 MidiKeyboardManager<V>::DeleteEventsLists();
232 DeleteGroupEventLists();
233
234 AudioOutputDevice* oldAudioDevice = pEngine->pAudioOutputDevice;
235 {
236 LockGuard lock(EngineMutex);
237 pEngine = NULL;
238 }
239 AbstractEngine::FreeEngine(this, oldAudioDevice);
240 AudioDeviceChannelLeft = -1;
241 AudioDeviceChannelRight = -1;
242 if (!fxSends.empty()) { // free the local rendering buffers
243 if (pChannelLeft) delete pChannelLeft;
244 if (pChannelRight) delete pChannelRight;
245 }
246 pChannelLeft = NULL;
247 pChannelRight = NULL;
248 }
249 }
250
251 class ClearEventListsHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
252 public:
253 virtual bool Process(MidiKey* pMidiKey) { pMidiKey->pEvents->clear(); return false; }
254 };
255
256 /**
257 * Free all events of the current audio fragment cycle. Calling
258 * this method will @b NOT free events scheduled past the current
259 * fragment's boundary! (@see AbstractEngineChannel::delayedEvents).
260 */
261 void ClearEventListsOfCurrentFragment() {
262 pEvents->clear();
263 // empty MIDI key specific event lists
264 ClearEventListsHandler handler;
265 this->ProcessActiveVoices(&handler);
266
267 // empty exclusive group specific event lists
268 // (pInstrument == 0 could mean that LoadInstrument is
269 // building new group event lists, so we must check
270 // for that)
271 if (pInstrument) ClearGroupEventLists();
272 }
273
274 // implementation of abstract methods derived from interface class 'InstrumentConsumer'
275
276 /**
277 * Will be called by the InstrumentResourceManager when the instrument
278 * we are currently using on this EngineChannel is going to be updated,
279 * so we can stop playback before that happens.
280 */
281 virtual void ResourceToBeUpdated(I* pResource, void*& pUpdateArg) OVERRIDE {
282 dmsg(3,("EngineChannelBase: Received instrument update message.\n"));
283 if (pEngine) pEngine->DisableAndLock();
284 ResetInternal(false/*don't reset engine*/);
285 this->pInstrument = NULL;
286 }
287
288 /**
289 * Will be called by the InstrumentResourceManager when the instrument
290 * update process was completed, so we can continue with playback.
291 */
292 virtual void ResourceUpdated(I* pOldResource, I* pNewResource, void* pUpdateArg) OVERRIDE {
293 this->pInstrument = pNewResource; //TODO: there are couple of engine parameters we should update here as well if the instrument was updated (see LoadInstrument())
294 if (pEngine) pEngine->Enable();
295 bStatusChanged = true; // status of engine has changed, so set notify flag
296 }
297
298 /**
299 * Will be called by the InstrumentResourceManager on progress changes
300 * while loading or realoading an instrument for this EngineChannel.
301 *
302 * @param fProgress - current progress as value between 0.0 and 1.0
303 */
304 virtual void OnResourceProgress(float fProgress) OVERRIDE {
305 this->InstrumentStat = int(fProgress * 100.0f);
306 dmsg(7,("EngineChannelBase: progress %d%%", InstrumentStat));
307 bStatusChanged = true; // status of engine has changed, so set notify flag
308 }
309
310 /**
311 * Called on sustain pedal up events to check and if required,
312 * launch release trigger voices on the respective active key.
313 *
314 * @param pEngineChannel - engine channel on which this event occurred on
315 * @param itEvent - release trigger event (contains note number)
316 */
317 virtual void ProcessReleaseTrigger(RTList<Event>::Iterator& itEvent) OVERRIDE {
318 if (!pEngine) return;
319 pEngine->ProcessReleaseTrigger(this, itEvent);
320 }
321
322 void RenderActiveVoices(uint Samples) {
323 RenderVoicesHandler handler(this, Samples);
324 this->ProcessActiveVoices(&handler);
325
326 SetVoiceCount(handler.VoiceCount);
327 SetDiskStreamCount(handler.StreamCount);
328 }
329
330 /**
331 * Called by real-time instrument script functions to schedule a
332 * new note (new note-on event and a new @c Note object linked to it)
333 * @a delay microseconds in future.
334 *
335 * @b IMPORTANT: for the supplied @a delay to be scheduled
336 * correctly, the passed @a pEvent must be assigned a valid
337 * fragment time within the current audio fragment boundaries. That
338 * fragment time will be used by this method as basis for
339 * interpreting what "now" acutally is, and thus it will be used as
340 * basis for calculating the precise scheduling time for @a delay.
341 * The easiest way to achieve this is by copying a recent event
342 * which happened within the current audio fragment cycle: i.e. the
343 * original event which caused calling this method here, or by using
344 * Event::copyTimefrom() method to only copy the time, without any
345 * other event data.
346 *
347 * @param pEvent - note-on event to be scheduled in future (event
348 * data will be copied)
349 * @param delay - amount of microseconds in future (from now) when
350 * event shall be processed
351 * @returns unique note ID of scheduled new note, or NULL on error
352 */
353 note_id_t ScheduleNoteMicroSec(const Event* pEvent, int delay) OVERRIDE {
354 // add (copied) note-on event into scheduler queue
355 const event_id_t noteOnEventID = ScheduleEventMicroSec(pEvent, delay);
356 if (!noteOnEventID) return 0; // error
357 // get access to (copied) event on the scheduler queue
358 RTList<Event>::Iterator itEvent = pEvents->fromID(noteOnEventID);
359 // stick a new note to the (copied) event on the queue
360 const note_id_t noteID = pEngine->LaunchNewNote(this, itEvent);
361 return noteID;
362 }
363
364 /**
365 * Called by real-time instrument script functions to ignore the note
366 * reflected by given note ID. The note's event will be freed immediately
367 * to its event pool and this will prevent voices to be launched for the
368 * note.
369 *
370 * NOTE: preventing a note by calling this method works only if the note
371 * was launched within the current audio fragment cycle.
372 *
373 * @param id - unique ID of note to be dropped
374 */
375 void IgnoreNote(note_id_t id) OVERRIDE {
376 Pool< Note<V> >* pNotePool =
377 dynamic_cast<NotePool<V>*>(pEngine)->GetNotePool();
378
379 NoteIterator itNote = pNotePool->fromID(id);
380 if (!itNote) return; // note probably already released
381
382 // if the note already got active voices, then it is too late to drop it
383 if (!itNote->pActiveVoices->isEmpty()) return;
384
385 // if the original (note-on) event is not available anymore, then it is too late to drop it
386 RTList<Event>::Iterator itEvent = pEvents->fromID(itNote->eventID);
387 if (!itEvent) return;
388
389 // drop the note
390 pNotePool->free(itNote);
391
392 // drop the original event
393 pEvents->free(itEvent);
394 }
395
396 /**
397 * Copies the note IDs of all currently active notes on this engine
398 * channel to the note ID buffer @a dstBuf, and returns the amount
399 * of note IDs that have been copied to the destination buffer.
400 *
401 * @param dstBuf - destination buffer for note IDs
402 * @param bufSize - size of destination buffer (as amount of max.
403 * note IDs, not as amount of bytes)
404 * @returns amount of note IDs that have been copied to buffer
405 */
406 uint AllNoteIDs(note_id_t* dstBuf, uint bufSize) OVERRIDE {
407 uint n = 0;
408
409 Pool< Note<V> >* pNotePool =
410 dynamic_cast<NotePool<V>*>(pEngine)->GetNotePool();
411
412 RTList<uint>::Iterator iuiKey = this->pActiveKeys->first();
413 RTList<uint>::Iterator end = this->pActiveKeys->end();
414 for(; iuiKey != end; ++iuiKey) {
415 MidiKey* pKey = &this->pMIDIKeyInfo[*iuiKey];
416 NoteIterator itNote = pKey->pActiveNotes->first();
417 for (; itNote; ++itNote) {
418 if (n >= bufSize) goto done;
419 dstBuf[n++] = pNotePool->getID(itNote);
420 }
421 }
422 done:
423 return n;
424 }
425
426 RTList<R*>* pRegionsInUse; ///< temporary pointer into the instrument change command, used by the audio thread
427 I* pInstrument;
428
429 template<class TV, class TRR, class TR, class TD, class TIM, class TI> friend class EngineBase;
430
431 protected:
432 EngineChannelBase() :
433 MidiKeyboardManager<V>(this),
434 InstrumentChangeCommandReader(InstrumentChangeCommand)
435 {
436 pInstrument = NULL;
437
438 // reset the instrument change command struct (need to be done
439 // twice, as it is double buffered)
440 {
441 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
442 cmd.pRegionsInUse = NULL;
443 cmd.pInstrument = NULL;
444 cmd.pScript = new InstrumentScript(this);
445 cmd.bChangeInstrument = false;
446 }
447 {
448 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
449 cmd.pRegionsInUse = NULL;
450 cmd.pInstrument = NULL;
451 cmd.pScript = new InstrumentScript(this);
452 cmd.bChangeInstrument = false;
453 }
454 }
455
456 virtual ~EngineChannelBase() {
457 InstrumentScript* previous = NULL; // prevent double free
458 {
459 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
460 if (cmd.pScript) {
461 previous = cmd.pScript;
462 delete cmd.pScript;
463 cmd.pScript = NULL;
464 }
465 }
466 {
467 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
468 if (cmd.pScript) {
469 if (previous != cmd.pScript)
470 delete cmd.pScript;
471 cmd.pScript = NULL;
472 }
473 }
474 }
475
476 typedef typename RTList<V>::Iterator RTListVoiceIterator;
477
478 class RenderVoicesHandler : public MidiKeyboardManager<V>::VoiceHandlerBase {
479 public:
480 uint Samples;
481 uint VoiceCount;
482 uint StreamCount;
483 EngineChannelBase<V, R, I>* pChannel;
484
485 RenderVoicesHandler(EngineChannelBase<V, R, I>* channel, uint samples) :
486 Samples(samples), VoiceCount(0), StreamCount(0), pChannel(channel) { }
487
488 virtual void Process(RTListVoiceIterator& itVoice) {
489 // now render current voice
490 itVoice->Render(Samples);
491 if (itVoice->IsActive()) { // still active
492 if (!itVoice->Orphan) {
493 *(pChannel->pRegionsInUse->allocAppend()) = itVoice->GetRegion();
494 }
495 VoiceCount++;
496
497 if (itVoice->PlaybackState == Voice::playback_state_disk) {
498 if ((itVoice->DiskStreamRef).State != Stream::state_unused) StreamCount++;
499 }
500 } else { // voice reached end, is now inactive
501 itVoice->VoiceFreed();
502 pChannel->FreeVoice(itVoice); // remove voice from the list of active voices
503 }
504 }
505 };
506
507 typedef typename SynchronizedConfig<InstrumentChangeCmd<R, I> >::Reader SyncConfInstrChangeCmdReader;
508
509 SynchronizedConfig<InstrumentChangeCmd<R, I> > InstrumentChangeCommand;
510 SyncConfInstrChangeCmdReader InstrumentChangeCommandReader;
511
512 /** This method is not thread safe! */
513 virtual void ResetInternal(bool bResetEngine) OVERRIDE {
514 AbstractEngineChannel::ResetInternal(bResetEngine);
515
516 MidiKeyboardManager<V>::Reset();
517 }
518
519 virtual void ResetControllers() OVERRIDE {
520 AbstractEngineChannel::ResetControllers();
521
522 MidiKeyboardManager<V>::SustainPedal = false;
523 MidiKeyboardManager<V>::SostenutoPedal = false;
524 }
525
526 /**
527 * Unload the currently used and loaded real-time instrument script.
528 * The source code of the script is retained, so that it can still
529 * be reloaded.
530 */
531 void UnloadScriptInUse() {
532 {
533 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
534 if (cmd.pScript) cmd.pScript->unload();
535 }
536 {
537 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.SwitchConfig();
538 if (cmd.pScript) cmd.pScript->unload();
539 }
540 InstrumentChangeCommand.SwitchConfig(); // switch back to original one
541 }
542
543 /**
544 * Load real-time instrument script and all its resources required
545 * for the upcoming instrument change.
546 *
547 * @param text - source code of script
548 */
549 void LoadInstrumentScript(const String& text) {
550 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
551 // load the new script
552 cmd.pScript->load(text);
553 }
554
555 /**
556 * Changes the instrument for an engine channel.
557 *
558 * @param pInstrument - new instrument
559 * @returns the resulting instrument change command after the
560 * command switch, containing the old instrument and
561 * the dimregions it is using
562 */
563 InstrumentChangeCmd<R, I>& ChangeInstrument(I* pInstrument) {
564 InstrumentChangeCmd<R, I>& cmd = InstrumentChangeCommand.GetConfigForUpdate();
565 cmd.pInstrument = pInstrument;
566 cmd.bChangeInstrument = true;
567
568 return InstrumentChangeCommand.SwitchConfig();
569 }
570
571 virtual void ProcessKeySwitchChange(int key) = 0;
572 };
573
574 } // namespace LinuxSampler
575
576 #endif /* __LS_ENGINECHANNELBASE_H__ */

  ViewVC Help
Powered by ViewVC