/[svn]/linuxsampler/trunk/src/engines/gig/InstrumentResourceManager.cpp
ViewVC logotype

Contents of /linuxsampler/trunk/src/engines/gig/InstrumentResourceManager.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3971 - (show annotations) (download)
Fri Jun 25 09:26:00 2021 UTC (2 years, 10 months ago) by schoenebeck
File size: 48608 byte(s)
* Fix: Do not share compiled instrument scripts among multiple engine
  channels (sampler parts) as this can lead to crashes and would allow the
  same global script variables to be accessible from multiple engine
  channels which would not be the expected behaviour by instrument script
  authors.

* Bumped version (2.2.0.svn5).

1
2 /***************************************************************************
3 * *
4 * LinuxSampler - modular, streaming capable sampler *
5 * *
6 * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck *
7 * Copyright (C) 2005 - 2021 Christian Schoenebeck *
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 * This program is distributed in the hope that it will be useful, *
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17 * GNU General Public License for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License *
20 * along with this program; if not, write to the Free Software *
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
22 * MA 02111-1307 USA *
23 ***************************************************************************/
24
25 #include <sstream>
26
27 #include "InstrumentResourceManager.h"
28 #include "EngineChannel.h"
29 #include "Engine.h"
30
31 #include "../../common/global_private.h"
32 #include "../../plugins/InstrumentEditorFactory.h"
33
34 namespace LinuxSampler { namespace gig {
35
36 // some data needed for the libgig callback function
37 struct progress_callback_arg_t {
38 InstrumentResourceManager* pManager;
39 InstrumentManager::instrument_id_t* pInstrumentKey;
40 };
41
42 // we use this to react on events concerning an instrument on behalf of an instrument editor
43 class InstrumentEditorProxy : public InstrumentConsumer {
44 public:
45 virtual ~InstrumentEditorProxy() {}
46
47 virtual void ResourceToBeUpdated(::gig::Instrument* pResource, void*& pUpdateArg) {
48 //TODO: inform the instrument editor about the pending update
49 }
50
51 virtual void ResourceUpdated(::gig::Instrument* pOldResource, ::gig::Instrument* pNewResource, void* pUpdateArg) {
52 //TODO:: inform the instrument editor about finished update
53 }
54
55 virtual void OnResourceProgress(float fProgress) {
56 //TODO: inform the instrument editor about the progress of an update
57 }
58
59 // the instrument we borrowed on behalf of the editor
60 ::gig::Instrument* pInstrument;
61 // the instrument editor we work on behalf
62 InstrumentEditor* pEditor;
63 };
64
65 /**
66 * Callback function which will be called by libgig during loading of
67 * instruments to inform about the current progress. Or to be more
68 * specific; it will be called during the GetInstrument() call.
69 *
70 * @param pProgress - contains current progress value, pointer to the
71 * InstrumentResourceManager instance and
72 * instrument ID
73 */
74 void InstrumentResourceManager::OnInstrumentLoadingProgress(::gig::progress_t* pProgress) {
75 dmsg(7,("gig::InstrumentResourceManager: progress %f%%", pProgress->factor));
76 progress_callback_arg_t* pArg = static_cast<progress_callback_arg_t*>(pProgress->custom);
77 // we randomly schedule 90% for the .gig file loading and the remaining 10% later for sample caching
78 const float localProgress = 0.9f * pProgress->factor;
79 pArg->pManager->DispatchResourceProgressEvent(*pArg->pInstrumentKey, localProgress);
80 }
81
82 String InstrumentResourceManager::GetInstrumentName(instrument_id_t ID) {
83 Lock();
84 ::gig::Instrument* pInstrument = Resource(ID, false);
85 String res = (pInstrument) ? pInstrument->pInfo->Name : "";
86 Unlock();
87 return res;
88 }
89
90 String InstrumentResourceManager::GetInstrumentDataStructureName(instrument_id_t ID) {
91 return ::gig::libraryName();
92 }
93
94 String InstrumentResourceManager::GetInstrumentDataStructureVersion(instrument_id_t ID) {
95 return ::gig::libraryVersion();
96 }
97
98 std::vector<InstrumentResourceManager::instrument_id_t> InstrumentResourceManager::GetInstrumentFileContent(String File) throw (InstrumentManagerException) {
99 ::RIFF::File* riff = NULL;
100 ::gig::File* gig = NULL;
101 try {
102 std::vector<instrument_id_t> result;
103 riff = new ::RIFF::File(File);
104 gig = new ::gig::File(riff);
105 gig->SetAutoLoad(false); // avoid time consuming samples scanning
106 for (int i = 0; gig->GetInstrument(i); i++) {
107 instrument_id_t id;
108 id.FileName = File;
109 id.Index = i;
110 result.push_back(id);
111 }
112 if (gig) delete gig;
113 if (riff) delete riff;
114 return result;
115 } catch (::RIFF::Exception e) {
116 if (gig) delete gig;
117 if (riff) delete riff;
118 throw InstrumentManagerException(e.Message);
119 } catch (...) {
120 if (gig) delete gig;
121 if (riff) delete riff;
122 throw InstrumentManagerException("Unknown exception while trying to parse '" + File + "'");
123 }
124 }
125
126 InstrumentResourceManager::instrument_info_t InstrumentResourceManager::GetInstrumentInfo(instrument_id_t ID) throw (InstrumentManagerException) {
127 Lock();
128 ::gig::Instrument* pInstrument = Resource(ID, false);
129 bool loaded = (pInstrument != NULL);
130 if (!loaded) Unlock();
131
132 ::RIFF::File* riff = NULL;
133 ::gig::File* gig = NULL;
134 try {
135 if (!loaded) {
136 riff = new ::RIFF::File(ID.FileName);
137 gig = new ::gig::File(riff);
138 gig->SetAutoLoad(false); // avoid time consuming samples scanning
139 pInstrument = gig->GetInstrument(ID.Index);
140 }
141
142 if (!pInstrument) throw InstrumentManagerException("There is no instrument " + ToString(ID.Index) + " in " + ID.FileName);
143
144 instrument_info_t info;
145 for (int i = 0; i < 128; i++) { info.KeyBindings[i] = info.KeySwitchBindings[i] = 0; }
146
147 ::gig::File* pFile = (::gig::File*) pInstrument->GetParent();
148
149 if (pFile->pVersion) {
150 info.FormatVersion = ToString(pFile->pVersion->major);
151 info.Product = pFile->pInfo->Product;
152 info.Artists = pFile->pInfo->Artists;
153 }
154
155 info.InstrumentName = pInstrument->pInfo->Name;
156
157 size_t iRgn = 0;
158 for (::gig::Region* pRegion = pInstrument->GetRegionAt(iRgn);
159 pRegion; pRegion = pInstrument->GetRegionAt(++iRgn))
160 {
161 int low = pRegion->KeyRange.low;
162 int high = pRegion->KeyRange.high;
163 if (low < 0 || low > 127 || high < 0 || high > 127 || low > high) {
164 std::cerr << "Invalid key range: " << low << " - " << high << std::endl;
165 } else {
166 for (int i = low; i <= high; i++) info.KeyBindings[i] = 1;
167 }
168 }
169
170 if (loaded) { // retrieve keyswitching only if the instrument is fully loaded.
171
172 // only return keyswitch range if keyswitching is used
173 bool hasKeyswitches = false;
174 size_t iRgn = 0;
175 for (::gig::Region* pRegion = pInstrument->GetRegionAt(iRgn);
176 pRegion && !hasKeyswitches ;
177 pRegion = pInstrument->GetRegionAt(++iRgn))
178 {
179 for (int i = 0 ; i < pRegion->Dimensions ; i++) {
180 if (pRegion->pDimensionDefinitions[i].dimension == ::gig::dimension_keyboard) {
181 hasKeyswitches = true;
182 break;
183 }
184 }
185 }
186
187 if (hasKeyswitches) {
188 int low = pInstrument->DimensionKeyRange.low;
189 int high = pInstrument->DimensionKeyRange.high;
190 if (low < 0 || low > 127 || high < 0 || high > 127 || low > high) {
191 std::cerr << "Invalid keyswitch range: " << low << " - " << high << std::endl;
192 } else {
193 for (int i = low; i <= high; i++) info.KeySwitchBindings[i] = 1;
194 }
195 }
196 }
197
198 if (loaded) Unlock();
199
200 if (gig) delete gig;
201 if (riff) delete riff;
202 return info;
203 } catch (::RIFF::Exception e) {
204 if (loaded) Unlock();
205 if (gig) delete gig;
206 if (riff) delete riff;
207 throw InstrumentManagerException(e.Message);
208 } catch (...) {
209 if (loaded) Unlock();
210 if (gig) delete gig;
211 if (riff) delete riff;
212 throw InstrumentManagerException("Unknown exception while trying to parse '" + ID.FileName + "'");
213 }
214 }
215
216 InstrumentEditor* InstrumentResourceManager::LaunchInstrumentEditor(LinuxSampler::EngineChannel* pEngineChannel, instrument_id_t ID, void* pUserData) throw (InstrumentManagerException) {
217 const String sDataType = GetInstrumentDataStructureName(ID);
218 const String sDataVersion = GetInstrumentDataStructureVersion(ID);
219 // find instrument editors capable to handle given instrument
220 std::vector<String> vEditors =
221 InstrumentEditorFactory::MatchingEditors(sDataType, sDataVersion);
222 if (!vEditors.size())
223 throw InstrumentManagerException(
224 "There is no instrument editor capable to handle this instrument"
225 );
226 // simply use the first editor in the result set
227 dmsg(1,("Found matching editor '%s' for instrument ('%s', %d) having data structure ('%s','%s')\n",
228 vEditors[0].c_str(), ID.FileName.c_str(), ID.Index, sDataType.c_str(), sDataVersion.c_str()));
229 InstrumentEditor* pEditor = InstrumentEditorFactory::Create(vEditors[0]);
230 // register for receiving notifications from the instrument editor
231 pEditor->AddListener(this);
232 // create a proxy that reacts on notification on behalf of the editor
233 InstrumentEditorProxy* pProxy = new InstrumentEditorProxy;
234 // borrow the instrument on behalf of the instrument editor
235 ::gig::Instrument* pInstrument = Borrow(ID, pProxy);
236 // remember the proxy and instrument for this instrument editor
237 pProxy->pInstrument = pInstrument;
238 pProxy->pEditor = pEditor;
239 InstrumentEditorProxiesMutex.Lock();
240 InstrumentEditorProxies.add(pProxy);
241 InstrumentEditorProxiesMutex.Unlock();
242 // launch the instrument editor for the given instrument
243 pEditor->Launch(pEngineChannel, pInstrument, sDataType, sDataVersion, pUserData);
244
245 // register the instrument editor as virtual MIDI device as well ...
246 VirtualMidiDevice* pVirtualMidiDevice =
247 dynamic_cast<VirtualMidiDevice*>(pEditor);
248 if (!pVirtualMidiDevice) {
249 std::cerr << "Instrument editor not a virtual MIDI device\n" << std::flush;
250 return pEditor;
251 }
252 // NOTE: for now connect the virtual MIDI keyboard of the instrument editor (if any) with all engine channels that have the same instrument as the editor was opened for ( other ideas ? )
253 Lock();
254 std::set<EngineChannel*> engineChannels =
255 GetEngineChannelsUsing(pInstrument, false/*don't lock again*/);
256 std::set<EngineChannel*>::iterator iter = engineChannels.begin();
257 std::set<EngineChannel*>::iterator end = engineChannels.end();
258 for (; iter != end; ++iter) (static_cast<AbstractEngineChannel*>(*iter))->Connect(pVirtualMidiDevice);
259 Unlock();
260
261 return pEditor;
262 }
263
264 /**
265 * Will be called by the respective instrument editor once it left its
266 * Main() loop. That way we can handle cleanup before its thread finally
267 * dies.
268 *
269 * @param pSender - instrument editor that stops execution
270 */
271 void InstrumentResourceManager::OnInstrumentEditorQuit(InstrumentEditor* pSender) {
272 dmsg(1,("InstrumentResourceManager: instrument editor quit, doing cleanup\n"));
273
274 ::gig::Instrument* pInstrument = NULL;
275 InstrumentEditorProxy* pProxy = NULL;
276 int iProxyIndex = -1;
277
278 // first find the editor proxy entry for this editor
279 {
280 LockGuard lock(InstrumentEditorProxiesMutex);
281 for (int i = 0; i < InstrumentEditorProxies.size(); i++) {
282 InstrumentEditorProxy* pCurProxy =
283 dynamic_cast<InstrumentEditorProxy*>(
284 InstrumentEditorProxies[i]
285 );
286 if (pCurProxy->pEditor == pSender) {
287 pProxy = pCurProxy;
288 iProxyIndex = i;
289 pInstrument = pCurProxy->pInstrument;
290 }
291 }
292 }
293
294 if (!pProxy) {
295 std::cerr << "Eeeek, could not find instrument editor proxy, "
296 "this is a bug!\n" << std::flush;
297 return;
298 }
299
300 // now unregister editor as not being available as a virtual MIDI device anymore
301 VirtualMidiDevice* pVirtualMidiDevice =
302 dynamic_cast<VirtualMidiDevice*>(pSender);
303 if (pVirtualMidiDevice) {
304 Lock();
305 // NOTE: see note in LaunchInstrumentEditor()
306 std::set<EngineChannel*> engineChannels =
307 GetEngineChannelsUsing(pInstrument, false/*don't lock again*/);
308 std::set<EngineChannel*>::iterator iter = engineChannels.begin();
309 std::set<EngineChannel*>::iterator end = engineChannels.end();
310 for (; iter != end; ++iter) (*iter)->Disconnect(pVirtualMidiDevice);
311 Unlock();
312 } else {
313 std::cerr << "Could not unregister editor as not longer acting as "
314 "virtual MIDI device. Wasn't it registered?\n"
315 << std::flush;
316 }
317
318 // finally delete proxy entry and hand back instrument
319 if (pInstrument) {
320 {
321 LockGuard lock(InstrumentEditorProxiesMutex);
322 InstrumentEditorProxies.remove(iProxyIndex);
323 }
324
325 HandBack(pInstrument, pProxy);
326 delete pProxy;
327 }
328
329 // Note that we don't need to free the editor here. As it
330 // derives from Thread, it will delete itself when the thread
331 // dies.
332 }
333
334 #if 0 // currently unused :
335 /**
336 * Try to inform the respective instrument editor(s), that a note on
337 * event just occured. This method is called by the MIDI thread. If any
338 * obstacles are in the way (e.g. if a wait for an unlock would be
339 * required) we give up immediately, since the RT safeness of the MIDI
340 * thread has absolute priority.
341 */
342 void InstrumentResourceManager::TrySendNoteOnToEditors(uint8_t Key, uint8_t Velocity, ::gig::Instrument* pInstrument) {
343 const bool bGotLock = InstrumentEditorProxiesMutex.Trylock(); // naively assumes RT safe implementation
344 if (!bGotLock) return; // hell, forget it, not worth the hassle
345 for (int i = 0; i < InstrumentEditorProxies.size(); i++) {
346 InstrumentEditorProxy* pProxy =
347 dynamic_cast<InstrumentEditorProxy*>(
348 InstrumentEditorProxies[i]
349 );
350 if (pProxy->pInstrument == pInstrument)
351 pProxy->pEditor->SendNoteOnToDevice(Key, Velocity);
352 }
353 InstrumentEditorProxiesMutex.Unlock(); // naively assumes RT safe implementation
354 }
355
356 /**
357 * Try to inform the respective instrument editor(s), that a note off
358 * event just occured. This method is called by the MIDI thread. If any
359 * obstacles are in the way (e.g. if a wait for an unlock would be
360 * required) we give up immediately, since the RT safeness of the MIDI
361 * thread has absolute priority.
362 */
363 void InstrumentResourceManager::TrySendNoteOffToEditors(uint8_t Key, uint8_t Velocity, ::gig::Instrument* pInstrument) {
364 const bool bGotLock = InstrumentEditorProxiesMutex.Trylock(); // naively assumes RT safe implementation
365 if (!bGotLock) return; // hell, forget it, not worth the hassle
366 for (int i = 0; i < InstrumentEditorProxies.size(); i++) {
367 InstrumentEditorProxy* pProxy =
368 dynamic_cast<InstrumentEditorProxy*>(
369 InstrumentEditorProxies[i]
370 );
371 if (pProxy->pInstrument == pInstrument)
372 pProxy->pEditor->SendNoteOffToDevice(Key, Velocity);
373 }
374 InstrumentEditorProxiesMutex.Unlock(); // naively assumes RT safe implementation
375 }
376 #endif // unused
377
378 void InstrumentResourceManager::OnSamplesToBeRemoved(std::set<void*> Samples, InstrumentEditor* pSender) {
379 if (Samples.empty()) {
380 std::cerr << "gig::InstrumentResourceManager: WARNING, "
381 "OnSamplesToBeRemoved() called with empty list, this "
382 "is a bug!\n" << std::flush;
383 return;
384 }
385 // TODO: ATM we assume here that all samples are from the same file
386 ::gig::Sample* pFirstSample = (::gig::Sample*) *Samples.begin();
387 ::gig::File* pCriticalFile = dynamic_cast< ::gig::File*>(pFirstSample->GetParent());
388 // completely suspend all engines that use that same file
389 SuspendEnginesUsing(pCriticalFile);
390 }
391
392 void InstrumentResourceManager::OnSamplesRemoved(InstrumentEditor* pSender) {
393 // resume all previously, completely suspended engines
394 // (we don't have to un-cache the removed samples here, since that is
395 // automatically done by the gig::Sample destructor)
396 ResumeAllEngines();
397 }
398
399 void InstrumentResourceManager::OnDataStructureToBeChanged(void* pStruct, String sStructType, InstrumentEditor* pSender) {
400 dmsg(5,("gig::InstrumentResourceManager::OnDataStructureToBeChanged(%s)\n", sStructType.c_str()));
401 //TODO: remove code duplication
402 if (sStructType == "gig::File") {
403 // completely suspend all engines that use that file
404 ::gig::File* pFile = (::gig::File*) pStruct;
405 SuspendEnginesUsing(pFile);
406 } else if (sStructType == "gig::Instrument") {
407 // completely suspend all engines that use that instrument
408 ::gig::Instrument* pInstrument = (::gig::Instrument*) pStruct;
409 SuspendEnginesUsing(pInstrument);
410 } else if (sStructType == "gig::Region") {
411 // only advice the engines to suspend the given region, so they'll
412 // only ignore that region (and probably already other suspended
413 // ones), but beside that continue normal playback
414 ::gig::Region* pRegion = (::gig::Region*) pStruct;
415 ::gig::Instrument* pInstrument =
416 (::gig::Instrument*) pRegion->GetParent();
417 Lock();
418 std::set<Engine*> engines =
419 GetEnginesUsing(pInstrument, false/*don't lock again*/);
420 std::set<Engine*>::iterator iter = engines.begin();
421 std::set<Engine*>::iterator end = engines.end();
422 for (; iter != end; ++iter) (*iter)->Suspend(pRegion);
423 Unlock();
424 } else if (sStructType == "gig::DimensionRegion") {
425 // only advice the engines to suspend the given DimensionRegions's
426 // parent region, so they'll only ignore that region (and probably
427 // already other suspended ones), but beside that continue normal
428 // playback
429 ::gig::DimensionRegion* pDimReg =
430 (::gig::DimensionRegion*) pStruct;
431 ::gig::Region* pRegion = pDimReg->GetParent();
432 ::gig::Instrument* pInstrument =
433 (::gig::Instrument*) pRegion->GetParent();
434 Lock();
435 std::set<Engine*> engines =
436 GetEnginesUsing(pInstrument, false/*don't lock again*/);
437 std::set<Engine*>::iterator iter = engines.begin();
438 std::set<Engine*>::iterator end = engines.end();
439 for (; iter != end; ++iter) (*iter)->Suspend(pRegion);
440 Unlock();
441 } else if (sStructType == "gig::Script") {
442 // no need to suspend anything here, since the sampler is
443 // processing a translated VM representation of the original script
444 // source code, not accessing the source code itself during playback
445 ::gig::Script* pScript = (::gig::Script*) pStruct;
446 // remember the original source code of the script, since the script
447 // resource manager uses the source code as key
448 pendingScriptUpdatesMutex.Lock();
449 pendingScriptUpdates[pScript] = pScript->GetScriptAsText();
450 pendingScriptUpdatesMutex.Unlock();
451 } else {
452 std::cerr << "gig::InstrumentResourceManager: ERROR, unknown data "
453 "structure '" << sStructType << "' requested to be "
454 "suspended by instrument editor. This is a bug!\n"
455 << std::flush;
456 //TODO: we should inform the instrument editor that something seriously went wrong
457 }
458 }
459
460 void InstrumentResourceManager::OnDataStructureChanged(void* pStruct, String sStructType, InstrumentEditor* pSender) {
461 dmsg(5,("gig::InstrumentResourceManager::OnDataStructureChanged(%s)\n", sStructType.c_str()));
462 //TODO: remove code duplication
463 if (sStructType == "gig::File") {
464 // resume all previously suspended engines
465 ResumeAllEngines();
466 } else if (sStructType == "gig::Instrument") {
467 // resume all previously suspended engines
468 ResumeAllEngines();
469 } else if (sStructType == "gig::Sample") {
470 // we're assuming here, that OnDataStructureToBeChanged() with
471 // "gig::File" was called previously, so we won't resume anything
472 // here, but just re-cache the given sample
473 Lock();
474 ::gig::Sample* pSample = (::gig::Sample*) pStruct;
475 ::gig::File* pFile = (::gig::File*) pSample->GetParent();
476 UncacheInitialSamples(pSample);
477 // now re-cache ...
478 std::vector< ::gig::Instrument*> instruments =
479 GetInstrumentsCurrentlyUsedOf(pFile, false/*don't lock again*/);
480 for (int i = 0; i < instruments.size(); i++) {
481 if (SampleReferencedByInstrument(pSample, instruments[i])) {
482 std::set<EngineChannel*> engineChannels =
483 GetEngineChannelsUsing(instruments[i], false/*don't lock again*/);
484 std::set<EngineChannel*>::iterator iter = engineChannels.begin();
485 std::set<EngineChannel*>::iterator end = engineChannels.end();
486 for (; iter != end; ++iter)
487 CacheInitialSamples(pSample, *iter);
488 }
489 }
490 Unlock();
491 } else if (sStructType == "gig::Region") {
492 // advice the engines to resume the given region, that is to
493 // using it for playback again
494 ::gig::Region* pRegion = (::gig::Region*) pStruct;
495 ::gig::Instrument* pInstrument =
496 (::gig::Instrument*) pRegion->GetParent();
497 Lock();
498 std::set<Engine*> engines =
499 GetEnginesUsing(pInstrument, false/*don't lock again*/);
500 std::set<Engine*>::iterator iter = engines.begin();
501 std::set<Engine*>::iterator end = engines.end();
502 for (; iter != end; ++iter) (*iter)->Resume(pRegion);
503 Unlock();
504 } else if (sStructType == "gig::DimensionRegion") {
505 // advice the engines to resume the given DimensionRegion's parent
506 // region, that is to using it for playback again
507 ::gig::DimensionRegion* pDimReg =
508 (::gig::DimensionRegion*) pStruct;
509 ::gig::Region* pRegion = pDimReg->GetParent();
510 ::gig::Instrument* pInstrument =
511 (::gig::Instrument*) pRegion->GetParent();
512 Lock();
513 std::set<Engine*> engines =
514 GetEnginesUsing(pInstrument, false/*don't lock again*/);
515 std::set<Engine*>::iterator iter = engines.begin();
516 std::set<Engine*>::iterator end = engines.end();
517 for (; iter != end; ++iter) (*iter)->Resume(pRegion);
518 Unlock();
519 } else if (sStructType == "gig::Script") {
520 // inform all engine channels which are using this script, that
521 // they need to reload (parse) the script's source code text
522 ::gig::Script* pScript = (::gig::Script*) pStruct;
523 pendingScriptUpdatesMutex.Lock();
524 if (pendingScriptUpdates.count(pScript)) {
525 const String& code = pendingScriptUpdates[pScript];
526 std::set<EngineChannel*> channels = GetEngineChannelsUsingScriptSourceCode(code, true/*lock*/);
527 pendingScriptUpdates.erase(pScript);
528 std::set<EngineChannel*>::iterator iter = channels.begin();
529 std::set<EngineChannel*>::iterator end = channels.end();
530 for (; iter != end; ++iter) (*iter)->reloadScript(pScript);
531 }
532 pendingScriptUpdatesMutex.Unlock();
533 } else {
534 std::cerr << "gig::InstrumentResourceManager: ERROR, unknown data "
535 "structure '" << sStructType << "' requested to be "
536 "resumed by instrument editor. This is a bug!\n"
537 << std::flush;
538 //TODO: we should inform the instrument editor that something seriously went wrong
539 }
540 }
541
542 void InstrumentResourceManager::OnSampleReferenceChanged(void* pOldSample, void* pNewSample, InstrumentEditor* pSender) {
543 // uncache old sample in case it's not used by anybody anymore
544 if (pOldSample) {
545 Lock();
546 ::gig::Sample* pSample = (::gig::Sample*) pOldSample;
547 ::gig::File* pFile = (::gig::File*) pSample->GetParent();
548 bool bSampleStillInUse = false;
549 std::vector< ::gig::Instrument*> instruments =
550 GetInstrumentsCurrentlyUsedOf(pFile, false/*don't lock again*/);
551 for (int i = 0; i < instruments.size(); i++) {
552 if (SampleReferencedByInstrument(pSample, instruments[i])) {
553 bSampleStillInUse = true;
554 break;
555 }
556 }
557 if (!bSampleStillInUse) UncacheInitialSamples(pSample);
558 Unlock();
559 }
560 // make sure new sample reference is cached
561 if (pNewSample) {
562 Lock();
563 ::gig::Sample* pSample = (::gig::Sample*) pNewSample;
564 ::gig::File* pFile = (::gig::File*) pSample->GetParent();
565 // get all engines that use that same gig::File
566 std::set<Engine*> engines = GetEnginesUsing(pFile, false/*don't lock again*/);
567 std::set<Engine*>::iterator iter = engines.begin();
568 std::set<Engine*>::iterator end = engines.end();
569 for (; iter != end; ++iter)
570 CacheInitialSamples(pSample, *iter);
571 Unlock();
572 }
573 }
574
575 ::gig::Instrument* InstrumentResourceManager::Create(instrument_id_t Key, InstrumentConsumer* pConsumer, void*& pArg) {
576 // get gig file from internal gig file manager
577 ::gig::File* pGig = Gigs.Borrow(Key.FileName, reinterpret_cast<GigConsumer*>(Key.Index)); // conversion kinda hackish :/
578
579 // we pass this to the progress callback mechanism of libgig
580 progress_callback_arg_t callbackArg;
581 callbackArg.pManager = this;
582 callbackArg.pInstrumentKey = &Key;
583
584 ::gig::progress_t progress;
585 progress.callback = OnInstrumentLoadingProgress;
586 progress.custom = &callbackArg;
587
588 dmsg(1,("Loading gig instrument ('%s',%d)...",Key.FileName.c_str(),Key.Index));
589 ::gig::Instrument* pInstrument = pGig->GetInstrument(Key.Index, &progress);
590 if (!pInstrument) {
591 std::stringstream msg;
592 msg << "There's no instrument with index " << Key.Index << ".";
593 throw InstrumentManagerException(msg.str());
594 }
595 pGig->GetSample(0); // just to force complete instrument loading
596 dmsg(1,("OK\n"));
597
598 uint maxSamplesPerCycle = GetMaxSamplesPerCycle(pConsumer);
599
600 // cache initial samples points (for actually needed samples)
601 dmsg(1,("Caching initial samples..."));
602 size_t iRegion = 0;
603 for (::gig::Region* pRgn = pInstrument->GetRegionAt(iRegion); pRgn;
604 pRgn = pInstrument->GetRegionAt(++iRegion))
605 {
606 // we randomly schedule 90% for the .gig file loading and the remaining 10% now for sample caching
607 const float localProgress = 0.9f + 0.1f * (float) iRegion / (float) pInstrument->Regions;
608 DispatchResourceProgressEvent(Key, localProgress);
609
610 if (pRgn->GetSample() && !pRgn->GetSample()->GetCache().Size) {
611 dmsg(2,("C"));
612 CacheInitialSamples(pRgn->GetSample(), maxSamplesPerCycle);
613 }
614 for (uint i = 0; i < pRgn->DimensionRegions; i++) {
615 CacheInitialSamples(pRgn->pDimensionRegions[i]->pSample, maxSamplesPerCycle);
616 }
617 }
618 dmsg(1,("OK\n"));
619 DispatchResourceProgressEvent(Key, 1.0f); // done; notify all consumers about progress 100%
620
621 // we need the following for destruction later
622 instr_entry_t* pEntry = new instr_entry_t;
623 pEntry->ID.FileName = Key.FileName;
624 pEntry->ID.Index = Key.Index;
625 pEntry->pFile = pGig;
626
627 // and we save this to check if we need to reallocate for an engine with higher value of 'MaxSamplesPerSecond'
628 pEntry->MaxSamplesPerCycle = maxSamplesPerCycle;
629
630 pArg = pEntry;
631
632 return pInstrument;
633 }
634
635 void InstrumentResourceManager::Destroy(::gig::Instrument* pResource, void* pArg) {
636 instr_entry_t* pEntry = (instr_entry_t*) pArg;
637 // we don't need the .gig file here anymore
638 Gigs.HandBack(pEntry->pFile, reinterpret_cast<GigConsumer*>(pEntry->ID.Index)); // conversion kinda hackish :/
639 delete pEntry;
640 }
641
642 void InstrumentResourceManager::DeleteRegionIfNotUsed(::gig::DimensionRegion* pRegion, region_info_t* pRegInfo) {
643 // TODO: we could delete Region and Instrument here if they have become unused
644 }
645
646 void InstrumentResourceManager::DeleteSampleIfNotUsed(::gig::Sample* pSample, region_info_t* pRegInfo) {
647 ::gig::File* gig = pRegInfo->file;
648 ::RIFF::File* riff = static_cast< ::RIFF::File*>(pRegInfo->pArg);
649 if (gig) {
650 gig->DeleteSample(pSample);
651 if (!gig->GetSample(0)) {
652 dmsg(2,("No more samples in use - freeing gig\n"));
653 delete gig;
654 delete riff;
655 }
656 }
657 }
658
659 /**
660 * Just a wrapper around the other @c CacheInitialSamples() method.
661 *
662 * @param pSample - points to the sample to be cached
663 * @param pEngine - pointer to Gig Engine Channel which caused this call
664 * (may be NULL, in this case default amount of samples
665 * will be cached)
666 */
667 void InstrumentResourceManager::CacheInitialSamples(::gig::Sample* pSample, EngineChannel* pEngineChannel) {
668 Engine* pEngine =
669 (pEngineChannel && pEngineChannel->GetEngine()) ?
670 dynamic_cast<Engine*>(pEngineChannel->GetEngine()) : NULL;
671 CacheInitialSamples(pSample, pEngine);
672 }
673
674 /**
675 * Caches a certain size at the beginning of the given sample in RAM. If the
676 * sample is very short, the whole sample will be loaded into RAM and thus
677 * no disk streaming is needed for this sample. Caching an initial part of
678 * samples is needed to compensate disk reading latency.
679 *
680 * @param pSample - points to the sample to be cached
681 * @param pEngine - pointer to Gig Engine which caused this call
682 * (may be NULL, in this case default amount of samples
683 * will be cached)
684 */
685 void InstrumentResourceManager::CacheInitialSamples(::gig::Sample* pSample, AbstractEngine* pEngine) {
686 uint maxSamplesPerCycle =
687 (pEngine) ? pEngine->pAudioOutputDevice->MaxSamplesPerCycle() :
688 DefaultMaxSamplesPerCycle();
689 CacheInitialSamples(pSample, maxSamplesPerCycle);
690 }
691
692 void InstrumentResourceManager::CacheInitialSamples(::gig::Sample* pSample, uint maxSamplesPerCycle) {
693 if (!pSample) {
694 dmsg(4,("gig::InstrumentResourceManager: Skipping sample (pSample == NULL)\n"));
695 return;
696 }
697 if (!pSample->SamplesTotal) return; // skip zero size samples
698
699 if (pSample->SamplesTotal <= CONFIG_PRELOAD_SAMPLES) {
700 // Sample is too short for disk streaming, so we load the whole
701 // sample into RAM and place 'pAudioIO->FragmentSize << CONFIG_MAX_PITCH'
702 // number of '0' samples (silence samples) behind the official buffer
703 // border, to allow the interpolator do it's work even at the end of
704 // the sample.
705 const uint neededSilenceSamples = uint((maxSamplesPerCycle << CONFIG_MAX_PITCH) + 6);
706 const uint currentlyCachedSilenceSamples = uint(pSample->GetCache().NullExtensionSize / pSample->FrameSize);
707 if (currentlyCachedSilenceSamples < neededSilenceSamples) {
708 dmsg(3,("Caching whole sample (sample name: \"%s\", sample size: %llu)\n", pSample->pInfo->Name.c_str(), (long long)pSample->SamplesTotal));
709 ::gig::buffer_t buf = pSample->LoadSampleDataWithNullSamplesExtension(neededSilenceSamples);
710 dmsg(4,("Cached %llu Bytes, %llu silence bytes.\n", (long long)buf.Size, (long long)buf.NullExtensionSize));
711 }
712 }
713 else { // we only cache CONFIG_PRELOAD_SAMPLES and stream the other sample points from disk
714 if (!pSample->GetCache().Size) pSample->LoadSampleData(CONFIG_PRELOAD_SAMPLES);
715 }
716
717 if (!pSample->GetCache().Size) std::cerr << "Unable to cache sample - maybe memory full!" << std::endl << std::flush;
718 }
719
720 void InstrumentResourceManager::UncacheInitialSamples(::gig::Sample* pSample) {
721 dmsg(1,("Uncaching sample %p\n",(void*)pSample));
722 if (pSample->GetCache().Size) pSample->ReleaseSampleData();
723 }
724
725 /**
726 * Returns a list with all instruments currently in use, that are part of
727 * the given file.
728 *
729 * @param pFile - search criteria
730 * @param bLock - whether we should lock (mutex) the instrument manager
731 * during this call and unlock at the end of this call
732 */
733 std::vector< ::gig::Instrument*> InstrumentResourceManager::GetInstrumentsCurrentlyUsedOf(::gig::File* pFile, bool bLock) {
734 if (bLock) Lock();
735 std::vector< ::gig::Instrument*> result;
736 std::vector< ::gig::Instrument*> allInstruments = Resources(false/*don't lock again*/);
737 for (int i = 0; i < allInstruments.size(); i++)
738 if (
739 (::gig::File*) allInstruments[i]->GetParent()
740 == pFile
741 ) result.push_back(allInstruments[i]);
742 if (bLock) Unlock();
743 return result;
744 }
745
746 /**
747 * Returns a list with all gig engine channels that are currently using
748 * the given real-time instrument script (provided as source code).
749 *
750 * @param pScript - search criteria
751 * @param bLock - whether we should lock (mutex) the instrument manager
752 * during this call and unlock at the end of this call
753 */
754 std::set<EngineChannel*> InstrumentResourceManager::GetEngineChannelsUsingScriptSourceCode(const String& code, bool bLock) {
755 if (bLock) Lock();
756 std::set<EngineChannel*> result;
757 std::set<InstrumentScriptConsumer*> consumers = scripts.ConsumersOf({
758 .code = code,
759 .patchVars = std::map<String,String>(), // just required for GCC
760 .engineChannel = NULL, // just required for GCC
761 .wildcardPatchVars = true,
762 .wildcardEngineChannel = true
763 });
764 std::set<InstrumentScriptConsumer*>::iterator iter = consumers.begin();
765 std::set<InstrumentScriptConsumer*>::iterator end = consumers.end();
766 for (; iter != end; ++iter) {
767 EngineChannel* pEngineChannel = dynamic_cast<EngineChannel*>(*iter);
768 if (!pEngineChannel) continue;
769 result.insert(pEngineChannel);
770 }
771 if (bLock) Unlock();
772 return result;
773 }
774
775 /**
776 * Returns a list with all gig engine channels that are currently using
777 * the given instrument.
778 *
779 * @param pInstrument - search criteria
780 * @param bLock - whether we should lock (mutex) the instrument manager
781 * during this call and unlock at the end of this call
782 */
783 std::set<EngineChannel*> InstrumentResourceManager::GetEngineChannelsUsing(::gig::Instrument* pInstrument, bool bLock) {
784 if (bLock) Lock();
785 std::set<EngineChannel*> result;
786 std::set<ResourceConsumer< ::gig::Instrument>*> consumers = ConsumersOf(pInstrument);
787 std::set<ResourceConsumer< ::gig::Instrument>*>::iterator iter = consumers.begin();
788 std::set<ResourceConsumer< ::gig::Instrument>*>::iterator end = consumers.end();
789 for (; iter != end; ++iter) {
790 EngineChannel* pEngineChannel = dynamic_cast<EngineChannel*>(*iter);
791 if (!pEngineChannel) continue;
792 result.insert(pEngineChannel);
793 }
794 if (bLock) Unlock();
795 return result;
796 }
797
798 /**
799 * Returns a list with all gig Engines that are currently using the given
800 * instrument.
801 *
802 * @param pInstrument - search criteria
803 * @param bLock - whether we should lock (mutex) the instrument manager
804 * during this call and unlock at the end of this call
805 */
806 std::set<Engine*> InstrumentResourceManager::GetEnginesUsing(::gig::Instrument* pInstrument, bool bLock) {
807 if (bLock) Lock();
808 std::set<Engine*> result;
809 std::set<ResourceConsumer< ::gig::Instrument>*> consumers = ConsumersOf(pInstrument);
810 std::set<ResourceConsumer< ::gig::Instrument>*>::iterator iter = consumers.begin();
811 std::set<ResourceConsumer< ::gig::Instrument>*>::iterator end = consumers.end();
812 for (; iter != end; ++iter) {
813 EngineChannel* pEngineChannel = dynamic_cast<EngineChannel*>(*iter);
814 if (!pEngineChannel) continue;
815 Engine* pEngine = dynamic_cast<Engine*>(pEngineChannel->GetEngine());
816 if (!pEngine) continue;
817 result.insert(pEngine);
818 }
819 if (bLock) Unlock();
820 return result;
821 }
822
823 /**
824 * Returns a list with all gig Engines that are currently using an
825 * instrument that is part of the given instrument file.
826 *
827 * @param pFile - search criteria
828 * @param bLock - whether we should lock (mutex) the instrument manager
829 * during this call and unlock at the end of this call
830 */
831 std::set<Engine*> InstrumentResourceManager::GetEnginesUsing(::gig::File* pFile, bool bLock) {
832 if (bLock) Lock();
833 // get all instruments (currently in usage) that use that same gig::File
834 std::vector< ::gig::Instrument*> instrumentsOfInterest =
835 GetInstrumentsCurrentlyUsedOf(pFile, false/*don't lock again*/);
836
837 // get all engines that use that same gig::File
838 std::set<Engine*> result;
839 {
840 for (int i = 0; i < instrumentsOfInterest.size(); i++) {
841 std::set<ResourceConsumer< ::gig::Instrument>*> consumers = ConsumersOf(instrumentsOfInterest[i]);
842 std::set<ResourceConsumer< ::gig::Instrument>*>::iterator iter = consumers.begin();
843 std::set<ResourceConsumer< ::gig::Instrument>*>::iterator end = consumers.end();
844 for (; iter != end; ++iter) {
845 EngineChannel* pEngineChannel = dynamic_cast<EngineChannel*>(*iter);
846 if (!pEngineChannel) continue;
847 Engine* pEngine = dynamic_cast<Engine*>(pEngineChannel->GetEngine());
848 if (!pEngine) continue;
849 // the unique, sorted container std::set makes
850 // sure we won't have duplicates
851 result.insert(pEngine);
852 }
853 }
854 }
855 if (bLock) Unlock();
856 return result;
857 }
858
859 /**
860 * Returns @c true in case the given sample is referenced somewhere by the
861 * given instrument, @c false otherwise.
862 *
863 * @param pSample - sample reference
864 * @param pInstrument - instrument that might use that sample
865 */
866 bool InstrumentResourceManager::SampleReferencedByInstrument(::gig::Sample* pSample, ::gig::Instrument* pInstrument) {
867 size_t iRgn = 0;
868 for (
869 ::gig::Region* pRegion = pInstrument->GetRegionAt(iRgn);
870 pRegion; pRegion = pInstrument->GetRegionAt(++iRgn)
871 ) {
872 for (
873 int i = 0; i < pRegion->DimensionRegions &&
874 pRegion->pDimensionRegions[i]; i++
875 ) {
876 if (pRegion->pDimensionRegions[i]->pSample == pSample)
877 return true;
878 }
879 }
880 return false;
881 }
882
883 /**
884 * Suspend all gig engines that use the given instrument. This means
885 * completely stopping playback on those engines and killing all their
886 * voices and disk streams. This method will block until all voices AND
887 * their disk streams are finally deleted and the engine turned into a
888 * complete idle loop.
889 *
890 * All @c SuspendEnginesUsing() methods only serve one thread by one and
891 * block all other threads until the current active thread called
892 * @c ResumeAllEngines() .
893 *
894 * @param pInstrument - search criteria
895 */
896 void InstrumentResourceManager::SuspendEnginesUsing(::gig::Instrument* pInstrument) {
897 // make sure no other thread suspends whole engines at the same time
898 suspendedEnginesMutex.Lock();
899 // get all engines that use that same gig::Instrument
900 suspendedEngines = GetEnginesUsing(pInstrument, true/*lock*/);
901 // finally, completely suspend all engines that use that same gig::Instrument
902 std::set<Engine*>::iterator iter = suspendedEngines.begin();
903 std::set<Engine*>::iterator end = suspendedEngines.end();
904 for (; iter != end; ++iter) (*iter)->SuspendAll();
905 }
906
907 /**
908 * Suspend all gig engines that use the given instrument file. This means
909 * completely stopping playback on those engines and killing all their
910 * voices and disk streams. This method will block until all voices AND
911 * their disk streams are finally deleted and the engine turned into a
912 * complete idle loop.
913 *
914 * All @c SuspendEnginesUsing() methods only serve one thread by one and
915 * block all other threads until the current active thread called
916 * @c ResumeAllEngines() .
917 *
918 * @param pFile - search criteria
919 */
920 void InstrumentResourceManager::SuspendEnginesUsing(::gig::File* pFile) {
921 // make sure no other thread suspends whole engines at the same time
922 suspendedEnginesMutex.Lock();
923 // get all engines that use that same gig::File
924 suspendedEngines = GetEnginesUsing(pFile, true/*lock*/);
925 // finally, completely suspend all engines that use that same gig::File
926 std::set<Engine*>::iterator iter = suspendedEngines.begin();
927 std::set<Engine*>::iterator end = suspendedEngines.end();
928 for (; iter != end; ++iter) (*iter)->SuspendAll();
929 }
930
931 /**
932 * MUST be called after one called one of the @c SuspendEnginesUsing()
933 * methods, to resume normal playback on all previously suspended engines.
934 * As it's only possible for one thread to suspend whole engines at the
935 * same time, this method doesn't take any arguments.
936 */
937 void InstrumentResourceManager::ResumeAllEngines() {
938 // resume all previously completely suspended engines
939 std::set<Engine*>::iterator iter = suspendedEngines.begin();
940 std::set<Engine*>::iterator end = suspendedEngines.end();
941 for (; iter != end; ++iter) (*iter)->ResumeAll();
942 // no more suspended engines ...
943 suspendedEngines.clear();
944 // allow another thread to suspend whole engines
945 suspendedEnginesMutex.Unlock();
946 }
947
948
949
950 // internal gig file manager
951
952 ::gig::File* InstrumentResourceManager::GigResourceManager::Create(String Key, GigConsumer* pConsumer, void*& pArg) {
953 dmsg(1,("Loading gig file \'%s\'...", Key.c_str()));
954 ::RIFF::File* pRIFF = new ::RIFF::File(Key);
955 // due to the multi-threaded scenario use separate file I/O handles for
956 // each thread to avoid file I/O concurrency issues with .gig file
957 pRIFF->SetIOPerThread(true);
958
959 ::gig::File* pGig = new ::gig::File(pRIFF);
960 pArg = pRIFF;
961 dmsg(1,("OK\n"));
962 return pGig;
963 }
964
965 void InstrumentResourceManager::GigResourceManager::Destroy(::gig::File* pResource, void* pArg) {
966 dmsg(1,("Freeing gig file '%s' from memory ...", pResource->GetFileName().c_str()));
967
968 // Delete as much as possible of the gig file. Some of the
969 // dimension regions and samples may still be in use - these
970 // will be deleted later by the HandBackDimReg function.
971 bool deleteFile = true;
972 ssize_t iInstr = pResource->CountInstruments();
973 for (--iInstr; iInstr >= 0; --iInstr) {
974 ::gig::Instrument* instrument = pResource->GetInstrument(iInstr);
975 bool deleteInstrument = true;
976 ssize_t iRgn = instrument->CountRegions();
977 for (--iRgn; iRgn >= 0; --iRgn) {
978 ::gig::Region* region = instrument->GetRegionAt(iRgn);
979 bool deleteRegion = true;
980 for (int i = 0 ; i < region->DimensionRegions ; i++)
981 {
982 ::gig::DimensionRegion *d = region->pDimensionRegions[i];
983 std::map< ::gig::DimensionRegion*, region_info_t>::iterator iter = parent->RegionInfo.find(d);
984 if (iter != parent->RegionInfo.end()) {
985 region_info_t& dimRegInfo = (*iter).second;
986 dimRegInfo.file = pResource;
987 dimRegInfo.pArg = (::RIFF::File*)pArg;
988 deleteFile = deleteInstrument = deleteRegion = false;
989 }
990 }
991 if (deleteRegion) instrument->DeleteRegion(region);
992 }
993 if (deleteInstrument) pResource->DeleteInstrument(instrument);
994 }
995 if (deleteFile) {
996 delete pResource;
997 delete (::RIFF::File*) pArg;
998 } else {
999 dmsg(2,("keeping some samples that are in use..."));
1000 ssize_t i = pResource->CountSamples();
1001 for (--i; i >= 0; --i) {
1002 ::gig::Sample* sample = pResource->GetSample(i);
1003 if (parent->SampleRefCount.find(sample) == parent->SampleRefCount.end()) {
1004 pResource->DeleteSample(sample);
1005 }
1006 }
1007 }
1008 dmsg(1,("OK\n"));
1009 }
1010
1011 }} // namespace LinuxSampler::gig

  ViewVC Help
Powered by ViewVC