/[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 2879 - (show annotations) (download) (as text)
Tue Apr 19 14:07:53 2016 UTC (8 years ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 23409 byte(s)
* All engines: Active voices are now internally grouped to "Note" objects,
  instead of being directly assigned to a keyboard key. This allows more
  fine graded processing of voices, which is i.e. required for certain
  instrument script features.
* Built-in script function "play_note()": Added support for passing
  special value -1 for "duration-us" argument, which will cause the
  triggered note to be released once the original note was released.
* Bumped version (2.0.0.svn3).

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

  ViewVC Help
Powered by ViewVC