1 |
iliev |
2012 |
/*************************************************************************** |
2 |
|
|
* * |
3 |
|
|
* LinuxSampler - modular, streaming capable sampler * |
4 |
|
|
* * |
5 |
|
|
* Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck * |
6 |
schoenebeck |
4019 |
* Copyright (C) 2005 - 2022 Christian Schoenebeck * |
7 |
|
|
* Copyright (C) 2009 - 2012 Grigor Iliev * |
8 |
iliev |
2012 |
* * |
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 "../../common/Path.h" |
33 |
|
|
#include "../../plugins/InstrumentEditorFactory.h" |
34 |
|
|
|
35 |
|
|
|
36 |
|
|
namespace LinuxSampler { namespace sfz { |
37 |
|
|
|
38 |
|
|
String InstrumentResourceManager::GetInstrumentName(instrument_id_t ID) { |
39 |
|
|
Lock(); |
40 |
|
|
::sfz::Instrument* pInstrument = Resource(ID, false); |
41 |
|
|
String res = (pInstrument) ? pInstrument->GetName() : ""; |
42 |
|
|
Unlock(); |
43 |
|
|
return res; |
44 |
|
|
} |
45 |
|
|
|
46 |
|
|
std::vector<InstrumentResourceManager::instrument_id_t> InstrumentResourceManager::GetInstrumentFileContent(String File) throw (InstrumentManagerException) { |
47 |
|
|
std::vector<instrument_id_t> result; |
48 |
|
|
instrument_id_t id; |
49 |
|
|
id.FileName = File; |
50 |
|
|
id.Index = 0; |
51 |
|
|
result.push_back(id); |
52 |
|
|
return result; |
53 |
|
|
} |
54 |
|
|
|
55 |
|
|
InstrumentResourceManager::instrument_info_t InstrumentResourceManager::GetInstrumentInfo(instrument_id_t ID) throw (InstrumentManagerException) { |
56 |
|
|
if (ID.Index) throw InstrumentManagerException("There is no instrument " + ToString(ID.Index) + " in " + ID.FileName); |
57 |
|
|
Lock(); |
58 |
|
|
::sfz::Instrument* pInstrument = Resource(ID, false); |
59 |
|
|
bool loaded = (pInstrument != NULL); |
60 |
|
|
if (!loaded) Unlock(); |
61 |
|
|
|
62 |
|
|
::sfz::File* sfz = NULL; |
63 |
|
|
try { |
64 |
persson |
2326 |
if (!loaded) { |
65 |
iliev |
2012 |
sfz = new ::sfz::File(ID.FileName); |
66 |
|
|
pInstrument = sfz->GetInstrument(); |
67 |
|
|
} |
68 |
|
|
|
69 |
|
|
if (!pInstrument) throw InstrumentManagerException("There is no instrument " + ToString(ID.Index) + " in " + ID.FileName); |
70 |
|
|
|
71 |
|
|
instrument_info_t info; |
72 |
|
|
info.InstrumentName = Path::getBaseName(ID.FileName); |
73 |
|
|
|
74 |
|
|
for (int i = 0; i < 128; i++) { |
75 |
|
|
info.KeyBindings[i] = pInstrument->HasKeyBinding(i); |
76 |
|
|
info.KeySwitchBindings[i] = pInstrument->HasKeySwitchBinding(i); |
77 |
|
|
} |
78 |
|
|
|
79 |
|
|
if (loaded) Unlock(); |
80 |
|
|
|
81 |
|
|
if (sfz) delete sfz; |
82 |
|
|
return info; |
83 |
|
|
} catch (::sfz::Exception e) { |
84 |
|
|
if (loaded) Unlock(); |
85 |
|
|
if (sfz) delete sfz; |
86 |
|
|
throw InstrumentManagerException(e.Message()); |
87 |
|
|
} catch (...) { |
88 |
|
|
if (loaded) Unlock(); |
89 |
|
|
if (sfz) delete sfz; |
90 |
|
|
throw InstrumentManagerException("Unknown exception while trying to parse '" + ID.FileName + "'"); |
91 |
|
|
} |
92 |
|
|
} |
93 |
|
|
|
94 |
|
|
::sfz::Instrument* InstrumentResourceManager::Create(instrument_id_t Key, InstrumentConsumer* pConsumer, void*& pArg) { |
95 |
|
|
// get sfz file from internal sfz file manager |
96 |
schoenebeck |
4019 |
::sfz::File* pSfz = Sfzs.Borrow(Key.FileName, &Sfzs); |
97 |
iliev |
2012 |
|
98 |
|
|
dmsg(1,("Loading sfz instrument ('%s',%d)...",Key.FileName.c_str(),Key.Index)); |
99 |
|
|
if (Key.Index) { |
100 |
|
|
std::stringstream msg; |
101 |
|
|
msg << "There's no instrument with index " << Key.Index << "."; |
102 |
|
|
throw InstrumentManagerException(msg.str()); |
103 |
|
|
} |
104 |
|
|
::sfz::Instrument* pInstrument = pSfz->GetInstrument(); |
105 |
|
|
if (!pInstrument) { |
106 |
|
|
std::stringstream msg; |
107 |
|
|
msg << "There's no instrument with index " << Key.Index << "."; |
108 |
|
|
throw InstrumentManagerException(msg.str()); |
109 |
|
|
} |
110 |
|
|
dmsg(1,("OK\n")); |
111 |
|
|
|
112 |
|
|
// cache initial samples points (for actually needed samples) |
113 |
|
|
dmsg(1,("Caching initial samples...")); |
114 |
schoenebeck |
3054 |
int regionCount = (int) pInstrument->regions.size(); |
115 |
persson |
2326 |
uint maxSamplesPerCycle = GetMaxSamplesPerCycle(pConsumer); |
116 |
|
|
for (int i = 0 ; i < regionCount ; i++) { |
117 |
|
|
float localProgress = (float) i / (float) regionCount; |
118 |
iliev |
2012 |
DispatchResourceProgressEvent(Key, localProgress); |
119 |
persson |
2326 |
CacheInitialSamples(pInstrument->regions[i]->GetSample(), maxSamplesPerCycle); |
120 |
iliev |
2012 |
//pInstrument->regions[i]->GetSample()->Close(); |
121 |
|
|
} |
122 |
|
|
dmsg(1,("OK\n")); |
123 |
|
|
DispatchResourceProgressEvent(Key, 1.0f); // done; notify all consumers about progress 100% |
124 |
|
|
|
125 |
|
|
// we need the following for destruction later |
126 |
|
|
instr_entry_t* pEntry = new instr_entry_t; |
127 |
|
|
pEntry->ID.FileName = Key.FileName; |
128 |
|
|
pEntry->ID.Index = Key.Index; |
129 |
persson |
2326 |
pEntry->pFile = pSfz; |
130 |
iliev |
2012 |
|
131 |
persson |
2326 |
// and we save this to check if we need to reallocate for an engine with higher value of 'MaxSamplesPerSecond' |
132 |
|
|
pEntry->MaxSamplesPerCycle = maxSamplesPerCycle; |
133 |
schoenebeck |
2275 |
|
134 |
iliev |
2012 |
pArg = pEntry; |
135 |
|
|
|
136 |
|
|
return pInstrument; |
137 |
|
|
} |
138 |
|
|
|
139 |
persson |
2326 |
void InstrumentResourceManager::Destroy(::sfz::Instrument* pResource, void* pArg) { |
140 |
iliev |
2012 |
instr_entry_t* pEntry = (instr_entry_t*) pArg; |
141 |
|
|
// we don't need the .sfz file here anymore |
142 |
persson |
2326 |
Sfzs.HandBack(pEntry->pFile, reinterpret_cast<SfzConsumer*>(pEntry->ID.Index)); // conversion kinda hackish :/ |
143 |
iliev |
2012 |
delete pEntry; |
144 |
|
|
} |
145 |
|
|
|
146 |
|
|
void InstrumentResourceManager::DeleteRegionIfNotUsed(::sfz::Region* pRegion, region_info_t* pRegInfo) { |
147 |
|
|
::sfz::File* file = pRegInfo->file; |
148 |
|
|
if (file == NULL) return; |
149 |
|
|
|
150 |
|
|
file->GetInstrument()->DestroyRegion(pRegion); |
151 |
persson |
2326 |
if (file->GetInstrument()->regions.empty()) { |
152 |
iliev |
2012 |
dmsg(2,("No more regions in use - freeing sfz\n")); |
153 |
|
|
delete file; |
154 |
|
|
} |
155 |
|
|
} |
156 |
|
|
|
157 |
|
|
void InstrumentResourceManager::DeleteSampleIfNotUsed(Sample* pSample, region_info_t* pRegInfo) { |
158 |
|
|
|
159 |
|
|
} |
160 |
|
|
|
161 |
|
|
|
162 |
|
|
|
163 |
|
|
// internal sfz file manager |
164 |
|
|
|
165 |
|
|
::sfz::File* InstrumentResourceManager::SfzResourceManager::Create(String Key, SfzConsumer* pConsumer, void*& pArg) { |
166 |
|
|
dmsg(1,("Loading sfz file \'%s\'...", Key.c_str())); |
167 |
|
|
::sfz::File* pSfz = new ::sfz::File(Key, &sampleManager); |
168 |
|
|
dmsg(1,("OK\n")); |
169 |
schoenebeck |
4019 |
InstrumentManagerThread* thread = parent->GetInstrumentManagerThread(); |
170 |
|
|
// let this code be executed periodically by instrument manager thread |
171 |
|
|
// in order to automatically reload this .sfz file whenever it was |
172 |
|
|
// modified, e.g. by some external text editor application |
173 |
|
|
thread->AddPeriodicJob("autoreload_sfz:" + Key, [this,Key,pSfz]{ |
174 |
|
|
if (pSfz->checkFileModified()) { |
175 |
|
|
dmsg(1,("Sfz file was modified: auto reloading '%s'\n", Key.c_str())); |
176 |
|
|
// passing NULL as consumer here instead of 'this' to ensure |
177 |
|
|
// that the ResourceToBeUpdated() and ResourceUpdated() method |
178 |
|
|
// pair below is executed |
179 |
|
|
Update(pSfz, NULL); |
180 |
|
|
} |
181 |
|
|
}); |
182 |
iliev |
2012 |
return pSfz; |
183 |
|
|
} |
184 |
|
|
|
185 |
|
|
void InstrumentResourceManager::SfzResourceManager::Destroy(::sfz::File* pResource, void* pArg) { |
186 |
|
|
dmsg(1,("Freeing sfz file from memory...")); |
187 |
|
|
|
188 |
schoenebeck |
4019 |
// stop periodic job previously scheduled above before freeing sfz file |
189 |
|
|
const String key = pResource->filename(); |
190 |
|
|
InstrumentManagerThread* thread = parent->GetInstrumentManagerThread(); |
191 |
|
|
thread->RemovePeriodicJob("autoreload_sfz:" + key); |
192 |
|
|
|
193 |
iliev |
2012 |
// Delete as much as possible of the sfz file. Some of the |
194 |
|
|
// regions and samples may still be in use - these |
195 |
|
|
// will be deleted later by the HandBackRegion function. |
196 |
|
|
bool deleteInstrument = true; |
197 |
|
|
::sfz::Instrument* pInstr = pResource->GetInstrument(); |
198 |
|
|
|
199 |
schoenebeck |
3054 |
for (int i = (int)pInstr->regions.size() - 1; i >= 0 ; i--) { |
200 |
iliev |
2012 |
::sfz::Region* pRegion = pInstr->regions[i]; |
201 |
|
|
std::map< ::sfz::Region*, region_info_t>::iterator iter = parent->RegionInfo.find(pRegion); |
202 |
|
|
if (iter != parent->RegionInfo.end()) { |
203 |
|
|
region_info_t& regInfo = (*iter).second; |
204 |
|
|
regInfo.file = pResource; |
205 |
|
|
deleteInstrument = false; |
206 |
|
|
} else { |
207 |
persson |
2058 |
pInstr->DestroyRegion(pRegion); |
208 |
iliev |
2012 |
} |
209 |
|
|
} |
210 |
|
|
|
211 |
schoenebeck |
4019 |
// free sfz file |
212 |
persson |
2326 |
if (deleteInstrument) delete pResource; |
213 |
iliev |
2012 |
else dmsg(2,("keeping some samples that are in use...")); |
214 |
|
|
|
215 |
|
|
dmsg(1,("OK\n")); |
216 |
|
|
} |
217 |
|
|
|
218 |
schoenebeck |
4019 |
void InstrumentResourceManager::SfzResourceManager::ResourceToBeUpdated(::sfz::File* pResource, void*& pUpdateArg) { |
219 |
|
|
// Since the respective sfz::Instrument objects will already be |
220 |
|
|
// invalidated when ResourceUpdated() will be called subsequently, we |
221 |
|
|
// must assemble a list of the current sfz::Instrument objects being |
222 |
|
|
// subject of the .sfz file changed already here. |
223 |
|
|
std::set<::sfz::Instrument*>* pInstruments = new std::set<::sfz::Instrument*>(); |
224 |
|
|
{ |
225 |
|
|
std::vector<::sfz::Instrument*> instrumentsInUse = parent->Resources(false); |
226 |
|
|
for (size_t i = 0; i < instrumentsInUse.size(); i++) { |
227 |
|
|
::sfz::File* f = instrumentsInUse[i]->GetFile(); |
228 |
|
|
if (f == pResource) pInstruments->insert(instrumentsInUse[i]); |
229 |
|
|
} |
230 |
|
|
} |
231 |
|
|
pUpdateArg = pInstruments; |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
void InstrumentResourceManager::SfzResourceManager::ResourceUpdated(::sfz::File* pOldResource, ::sfz::File* pNewResource, void* pUpdateArg) { |
235 |
|
|
// force reload sfz::Instrument objects on engine channels that used that sfz file |
236 |
|
|
std::set<::sfz::Instrument*>* pInstruments = (std::set<::sfz::Instrument*>*)pUpdateArg; |
237 |
|
|
std::set<::sfz::Instrument*>::const_iterator it = pInstruments->begin(); |
238 |
|
|
std::set<::sfz::Instrument*>::const_iterator end = pInstruments->end(); |
239 |
|
|
for (; it != end; ++it) { |
240 |
|
|
::sfz::Instrument* instrument = *it; |
241 |
|
|
parent->Update(instrument, reinterpret_cast<SfzInstrConsumer*>(this)); |
242 |
|
|
} |
243 |
|
|
delete pInstruments; |
244 |
|
|
} |
245 |
|
|
|
246 |
iliev |
2012 |
}} // namespace LinuxSampler::sfz |