Chris@320
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@320
|
2
|
Chris@320
|
3 /*
|
Chris@320
|
4 Sonic Visualiser
|
Chris@320
|
5 An audio file viewer and annotation editor.
|
Chris@320
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@320
|
7 This file copyright 2006 Chris Cannam and QMUL.
|
Chris@320
|
8
|
Chris@320
|
9 This program is free software; you can redistribute it and/or
|
Chris@320
|
10 modify it under the terms of the GNU General Public License as
|
Chris@320
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@320
|
12 License, or (at your option) any later version. See the file
|
Chris@320
|
13 COPYING included with this distribution for more information.
|
Chris@320
|
14 */
|
Chris@320
|
15
|
Chris@331
|
16 #include "ModelTransformerFactory.h"
|
Chris@320
|
17
|
Chris@331
|
18 #include "FeatureExtractionModelTransformer.h"
|
Chris@331
|
19 #include "RealTimeEffectModelTransformer.h"
|
Chris@320
|
20
|
Chris@332
|
21 #include "TransformFactory.h"
|
Chris@332
|
22
|
Chris@320
|
23 #include "plugin/FeatureExtractionPluginFactory.h"
|
Chris@320
|
24 #include "plugin/RealTimePluginFactory.h"
|
Chris@320
|
25 #include "plugin/PluginXml.h"
|
Chris@320
|
26
|
Chris@320
|
27 #include "widgets/PluginParameterDialog.h"
|
Chris@320
|
28
|
Chris@320
|
29 #include "data/model/DenseTimeValueModel.h"
|
Chris@320
|
30
|
Chris@320
|
31 #include "vamp-sdk/PluginHostAdapter.h"
|
Chris@320
|
32
|
Chris@320
|
33 #include "audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here
|
Chris@320
|
34
|
Chris@320
|
35 #include <iostream>
|
Chris@320
|
36 #include <set>
|
Chris@320
|
37
|
Chris@320
|
38 #include <QRegExp>
|
Chris@320
|
39
|
Chris@331
|
40 ModelTransformerFactory *
|
Chris@331
|
41 ModelTransformerFactory::m_instance = new ModelTransformerFactory;
|
Chris@320
|
42
|
Chris@331
|
43 ModelTransformerFactory *
|
Chris@331
|
44 ModelTransformerFactory::getInstance()
|
Chris@320
|
45 {
|
Chris@320
|
46 return m_instance;
|
Chris@320
|
47 }
|
Chris@320
|
48
|
Chris@331
|
49 ModelTransformerFactory::~ModelTransformerFactory()
|
Chris@320
|
50 {
|
Chris@320
|
51 }
|
Chris@320
|
52
|
Chris@320
|
53 bool
|
Chris@331
|
54 ModelTransformerFactory::getChannelRange(TransformId identifier, Vamp::PluginBase *plugin,
|
Chris@332
|
55 int &minChannels, int &maxChannels)
|
Chris@320
|
56 {
|
Chris@320
|
57 Vamp::Plugin *vp = 0;
|
Chris@320
|
58 if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
|
Chris@320
|
59 (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
|
Chris@320
|
60 minChannels = vp->getMinChannelCount();
|
Chris@320
|
61 maxChannels = vp->getMaxChannelCount();
|
Chris@320
|
62 return true;
|
Chris@320
|
63 } else {
|
Chris@332
|
64 return TransformFactory::getInstance()->
|
Chris@332
|
65 getTransformChannelRange(identifier, minChannels, maxChannels);
|
Chris@320
|
66 }
|
Chris@320
|
67 }
|
Chris@320
|
68
|
Chris@320
|
69 Model *
|
Chris@331
|
70 ModelTransformerFactory::getConfigurationForTransformer(TransformId identifier,
|
Chris@345
|
71 const std::vector<Model *> &candidateInputModels,
|
Chris@345
|
72 Model *defaultInputModel,
|
Chris@345
|
73 PluginTransformer::ExecutionContext &context,
|
Chris@345
|
74 QString &configurationXml,
|
Chris@345
|
75 AudioCallbackPlaySource *source,
|
Chris@345
|
76 size_t startFrame,
|
Chris@345
|
77 size_t duration)
|
Chris@320
|
78 {
|
Chris@320
|
79 if (candidateInputModels.empty()) return 0;
|
Chris@320
|
80
|
Chris@320
|
81 //!!! This will need revision -- we'll have to have a callback
|
Chris@320
|
82 //from the dialog for when the candidate input model is changed,
|
Chris@320
|
83 //as we'll need to reinitialise the channel settings in the dialog
|
Chris@345
|
84 Model *inputModel = candidateInputModels[0];
|
Chris@320
|
85 QStringList candidateModelNames;
|
Chris@345
|
86 QString defaultModelName;
|
Chris@320
|
87 std::map<QString, Model *> modelMap;
|
Chris@320
|
88 for (size_t i = 0; i < candidateInputModels.size(); ++i) {
|
Chris@320
|
89 QString modelName = candidateInputModels[i]->objectName();
|
Chris@320
|
90 QString origModelName = modelName;
|
Chris@320
|
91 int dupcount = 1;
|
Chris@320
|
92 while (modelMap.find(modelName) != modelMap.end()) {
|
Chris@320
|
93 modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount);
|
Chris@320
|
94 }
|
Chris@320
|
95 modelMap[modelName] = candidateInputModels[i];
|
Chris@320
|
96 candidateModelNames.push_back(modelName);
|
Chris@345
|
97 if (candidateInputModels[i] == defaultInputModel) {
|
Chris@345
|
98 defaultModelName = modelName;
|
Chris@345
|
99 }
|
Chris@320
|
100 }
|
Chris@320
|
101
|
Chris@320
|
102 QString id = identifier.section(':', 0, 2);
|
Chris@320
|
103 QString output = identifier.section(':', 3);
|
Chris@320
|
104 QString outputLabel = "";
|
Chris@320
|
105 QString outputDescription = "";
|
Chris@320
|
106
|
Chris@320
|
107 bool ok = false;
|
Chris@320
|
108 configurationXml = m_lastConfigurations[identifier];
|
Chris@320
|
109
|
Chris@320
|
110 // std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
|
Chris@320
|
111
|
Chris@320
|
112 Vamp::PluginBase *plugin = 0;
|
Chris@320
|
113
|
Chris@320
|
114 bool frequency = false;
|
Chris@320
|
115 bool effect = false;
|
Chris@320
|
116 bool generator = false;
|
Chris@320
|
117
|
Chris@320
|
118 if (FeatureExtractionPluginFactory::instanceFor(id)) {
|
Chris@320
|
119
|
Chris@328
|
120 std::cerr << "getConfigurationForTransformer: instantiating Vamp plugin" << std::endl;
|
Chris@320
|
121
|
Chris@320
|
122 Vamp::Plugin *vp =
|
Chris@320
|
123 FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
|
Chris@320
|
124 (id, inputModel->getSampleRate());
|
Chris@320
|
125
|
Chris@320
|
126 if (vp) {
|
Chris@320
|
127
|
Chris@320
|
128 plugin = vp;
|
Chris@320
|
129 frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
|
Chris@320
|
130
|
Chris@320
|
131 std::vector<Vamp::Plugin::OutputDescriptor> od =
|
Chris@320
|
132 vp->getOutputDescriptors();
|
Chris@320
|
133 if (od.size() > 1) {
|
Chris@320
|
134 for (size_t i = 0; i < od.size(); ++i) {
|
Chris@320
|
135 if (od[i].identifier == output.toStdString()) {
|
Chris@320
|
136 outputLabel = od[i].name.c_str();
|
Chris@320
|
137 outputDescription = od[i].description.c_str();
|
Chris@320
|
138 break;
|
Chris@320
|
139 }
|
Chris@320
|
140 }
|
Chris@320
|
141 }
|
Chris@320
|
142 }
|
Chris@320
|
143
|
Chris@320
|
144 } else if (RealTimePluginFactory::instanceFor(id)) {
|
Chris@320
|
145
|
Chris@320
|
146 RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id);
|
Chris@320
|
147 const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id);
|
Chris@320
|
148
|
Chris@320
|
149 if (desc->audioInputPortCount > 0 &&
|
Chris@320
|
150 desc->audioOutputPortCount > 0 &&
|
Chris@320
|
151 !desc->isSynth) {
|
Chris@320
|
152 effect = true;
|
Chris@320
|
153 }
|
Chris@320
|
154
|
Chris@320
|
155 if (desc->audioInputPortCount == 0) {
|
Chris@320
|
156 generator = true;
|
Chris@320
|
157 }
|
Chris@320
|
158
|
Chris@320
|
159 if (output != "A") {
|
Chris@320
|
160 int outputNo = output.toInt();
|
Chris@320
|
161 if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) {
|
Chris@320
|
162 outputLabel = desc->controlOutputPortNames[outputNo].c_str();
|
Chris@320
|
163 }
|
Chris@320
|
164 }
|
Chris@320
|
165
|
Chris@320
|
166 size_t sampleRate = inputModel->getSampleRate();
|
Chris@320
|
167 size_t blockSize = 1024;
|
Chris@320
|
168 size_t channels = 1;
|
Chris@320
|
169 if (effect && source) {
|
Chris@320
|
170 sampleRate = source->getTargetSampleRate();
|
Chris@320
|
171 blockSize = source->getTargetBlockSize();
|
Chris@320
|
172 channels = source->getTargetChannelCount();
|
Chris@320
|
173 }
|
Chris@320
|
174
|
Chris@320
|
175 RealTimePluginInstance *rtp = factory->instantiatePlugin
|
Chris@320
|
176 (id, 0, 0, sampleRate, blockSize, channels);
|
Chris@320
|
177
|
Chris@320
|
178 plugin = rtp;
|
Chris@320
|
179
|
Chris@320
|
180 if (effect && source && rtp) {
|
Chris@320
|
181 source->setAuditioningPlugin(rtp);
|
Chris@320
|
182 }
|
Chris@320
|
183 }
|
Chris@320
|
184
|
Chris@320
|
185 if (plugin) {
|
Chris@320
|
186
|
Chris@328
|
187 context = PluginTransformer::ExecutionContext(context.channel, plugin);
|
Chris@320
|
188
|
Chris@320
|
189 if (configurationXml != "") {
|
Chris@320
|
190 PluginXml(plugin).setParametersFromXml(configurationXml);
|
Chris@320
|
191 }
|
Chris@320
|
192
|
Chris@320
|
193 int sourceChannels = 1;
|
Chris@320
|
194 if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
|
Chris@320
|
195 sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
|
Chris@320
|
196 ->getChannelCount();
|
Chris@320
|
197 }
|
Chris@320
|
198
|
Chris@320
|
199 int minChannels = 1, maxChannels = sourceChannels;
|
Chris@320
|
200 getChannelRange(identifier, plugin, minChannels, maxChannels);
|
Chris@320
|
201
|
Chris@320
|
202 int targetChannels = sourceChannels;
|
Chris@320
|
203 if (!effect) {
|
Chris@320
|
204 if (sourceChannels < minChannels) targetChannels = minChannels;
|
Chris@320
|
205 if (sourceChannels > maxChannels) targetChannels = maxChannels;
|
Chris@320
|
206 }
|
Chris@320
|
207
|
Chris@320
|
208 int defaultChannel = context.channel;
|
Chris@320
|
209
|
Chris@320
|
210 PluginParameterDialog *dialog = new PluginParameterDialog(plugin);
|
Chris@320
|
211
|
Chris@320
|
212 if (candidateModelNames.size() > 1 && !generator) {
|
Chris@345
|
213 dialog->setCandidateInputModels(candidateModelNames,
|
Chris@345
|
214 defaultModelName);
|
Chris@320
|
215 }
|
Chris@320
|
216
|
Chris@320
|
217 if (startFrame != 0 || duration != 0) {
|
Chris@320
|
218 dialog->setShowSelectionOnlyOption(true);
|
Chris@320
|
219 }
|
Chris@320
|
220
|
Chris@320
|
221 if (targetChannels > 0) {
|
Chris@320
|
222 dialog->setChannelArrangement(sourceChannels, targetChannels,
|
Chris@320
|
223 defaultChannel);
|
Chris@320
|
224 }
|
Chris@320
|
225
|
Chris@320
|
226 dialog->setOutputLabel(outputLabel, outputDescription);
|
Chris@320
|
227
|
Chris@320
|
228 dialog->setShowProcessingOptions(true, frequency);
|
Chris@320
|
229
|
Chris@320
|
230 if (dialog->exec() == QDialog::Accepted) {
|
Chris@320
|
231 ok = true;
|
Chris@320
|
232 }
|
Chris@320
|
233
|
Chris@320
|
234 QString selectedInput = dialog->getInputModel();
|
Chris@320
|
235 if (selectedInput != "") {
|
Chris@320
|
236 if (modelMap.find(selectedInput) != modelMap.end()) {
|
Chris@320
|
237 inputModel = modelMap[selectedInput];
|
Chris@320
|
238 std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl;
|
Chris@320
|
239 } else {
|
Chris@320
|
240 std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl;
|
Chris@320
|
241 }
|
Chris@320
|
242 } else {
|
Chris@320
|
243 std::cerr << "Selected input empty: \"" << selectedInput.toStdString() << "\"" << std::endl;
|
Chris@320
|
244 }
|
Chris@320
|
245
|
Chris@320
|
246 configurationXml = PluginXml(plugin).toXmlString();
|
Chris@320
|
247 context.channel = dialog->getChannel();
|
Chris@320
|
248
|
Chris@320
|
249 if (startFrame != 0 || duration != 0) {
|
Chris@320
|
250 if (dialog->getSelectionOnly()) {
|
Chris@320
|
251 context.startFrame = startFrame;
|
Chris@320
|
252 context.duration = duration;
|
Chris@320
|
253 }
|
Chris@320
|
254 }
|
Chris@320
|
255
|
Chris@320
|
256 dialog->getProcessingParameters(context.stepSize,
|
Chris@320
|
257 context.blockSize,
|
Chris@320
|
258 context.windowType);
|
Chris@320
|
259
|
Chris@320
|
260 context.makeConsistentWithPlugin(plugin);
|
Chris@320
|
261
|
Chris@320
|
262 delete dialog;
|
Chris@320
|
263
|
Chris@320
|
264 if (effect && source) {
|
Chris@320
|
265 source->setAuditioningPlugin(0); // will delete our plugin
|
Chris@320
|
266 } else {
|
Chris@320
|
267 delete plugin;
|
Chris@320
|
268 }
|
Chris@320
|
269 }
|
Chris@320
|
270
|
Chris@320
|
271 if (ok) m_lastConfigurations[identifier] = configurationXml;
|
Chris@320
|
272
|
Chris@320
|
273 return ok ? inputModel : 0;
|
Chris@320
|
274 }
|
Chris@320
|
275
|
Chris@328
|
276 PluginTransformer::ExecutionContext
|
Chris@331
|
277 ModelTransformerFactory::getDefaultContextForTransformer(TransformId identifier,
|
Chris@320
|
278 Model *inputModel)
|
Chris@320
|
279 {
|
Chris@328
|
280 PluginTransformer::ExecutionContext context(-1);
|
Chris@320
|
281
|
Chris@320
|
282 QString id = identifier.section(':', 0, 2);
|
Chris@320
|
283
|
Chris@320
|
284 if (FeatureExtractionPluginFactory::instanceFor(id)) {
|
Chris@320
|
285
|
Chris@320
|
286 Vamp::Plugin *vp =
|
Chris@320
|
287 FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
|
Chris@320
|
288 (id, inputModel ? inputModel->getSampleRate() : 48000);
|
Chris@320
|
289
|
Chris@320
|
290 if (vp) {
|
Chris@328
|
291 context = PluginTransformer::ExecutionContext(-1, vp);
|
Chris@320
|
292 delete vp;
|
Chris@320
|
293 }
|
Chris@320
|
294 }
|
Chris@320
|
295
|
Chris@320
|
296 return context;
|
Chris@320
|
297 }
|
Chris@320
|
298
|
Chris@331
|
299 ModelTransformer *
|
Chris@331
|
300 ModelTransformerFactory::createTransformer(TransformId identifier, Model *inputModel,
|
Chris@328
|
301 const PluginTransformer::ExecutionContext &context,
|
Chris@320
|
302 QString configurationXml)
|
Chris@320
|
303 {
|
Chris@331
|
304 ModelTransformer *transformer = 0;
|
Chris@320
|
305
|
Chris@320
|
306 QString id = identifier.section(':', 0, 2);
|
Chris@320
|
307 QString output = identifier.section(':', 3);
|
Chris@320
|
308
|
Chris@320
|
309 if (FeatureExtractionPluginFactory::instanceFor(id)) {
|
Chris@332
|
310 transformer = new FeatureExtractionModelTransformer
|
Chris@332
|
311 (inputModel, id, context, configurationXml, output);
|
Chris@331
|
312 } else if (RealTimePluginFactory::instanceFor(id)) {
|
Chris@332
|
313 transformer = new RealTimeEffectModelTransformer
|
Chris@332
|
314 (inputModel, id, context, configurationXml,
|
Chris@332
|
315 TransformFactory::getInstance()->getTransformUnits(identifier),
|
Chris@332
|
316 output == "A" ? -1 : output.toInt());
|
Chris@320
|
317 } else {
|
Chris@331
|
318 std::cerr << "ModelTransformerFactory::createTransformer: Unknown transform \""
|
Chris@320
|
319 << identifier.toStdString() << "\"" << std::endl;
|
Chris@331
|
320 return transformer;
|
Chris@320
|
321 }
|
Chris@320
|
322
|
Chris@331
|
323 if (transformer) transformer->setObjectName(identifier);
|
Chris@331
|
324 return transformer;
|
Chris@320
|
325 }
|
Chris@320
|
326
|
Chris@320
|
327 Model *
|
Chris@331
|
328 ModelTransformerFactory::transform(TransformId identifier, Model *inputModel,
|
Chris@328
|
329 const PluginTransformer::ExecutionContext &context,
|
Chris@320
|
330 QString configurationXml)
|
Chris@320
|
331 {
|
Chris@331
|
332 ModelTransformer *t = createTransformer(identifier, inputModel, context,
|
Chris@331
|
333 configurationXml);
|
Chris@320
|
334
|
Chris@320
|
335 if (!t) return 0;
|
Chris@320
|
336
|
Chris@331
|
337 connect(t, SIGNAL(finished()), this, SLOT(transformerFinished()));
|
Chris@320
|
338
|
Chris@328
|
339 m_runningTransformers.insert(t);
|
Chris@320
|
340
|
Chris@320
|
341 t->start();
|
Chris@320
|
342 Model *model = t->detachOutputModel();
|
Chris@320
|
343
|
Chris@320
|
344 if (model) {
|
Chris@320
|
345 QString imn = inputModel->objectName();
|
Chris@332
|
346 QString trn =
|
Chris@332
|
347 TransformFactory::getInstance()->getTransformFriendlyName
|
Chris@332
|
348 (identifier);
|
Chris@320
|
349 if (imn != "") {
|
Chris@320
|
350 if (trn != "") {
|
Chris@320
|
351 model->setObjectName(tr("%1: %2").arg(imn).arg(trn));
|
Chris@320
|
352 } else {
|
Chris@320
|
353 model->setObjectName(imn);
|
Chris@320
|
354 }
|
Chris@320
|
355 } else if (trn != "") {
|
Chris@320
|
356 model->setObjectName(trn);
|
Chris@320
|
357 }
|
Chris@320
|
358 } else {
|
Chris@320
|
359 t->wait();
|
Chris@320
|
360 }
|
Chris@320
|
361
|
Chris@320
|
362 return model;
|
Chris@320
|
363 }
|
Chris@320
|
364
|
Chris@320
|
365 void
|
Chris@331
|
366 ModelTransformerFactory::transformerFinished()
|
Chris@320
|
367 {
|
Chris@320
|
368 QObject *s = sender();
|
Chris@331
|
369 ModelTransformer *transformer = dynamic_cast<ModelTransformer *>(s);
|
Chris@320
|
370
|
Chris@331
|
371 std::cerr << "ModelTransformerFactory::transformerFinished(" << transformer << ")" << std::endl;
|
Chris@320
|
372
|
Chris@331
|
373 if (!transformer) {
|
Chris@331
|
374 std::cerr << "WARNING: ModelTransformerFactory::transformerFinished: sender is not a transformer" << std::endl;
|
Chris@320
|
375 return;
|
Chris@320
|
376 }
|
Chris@320
|
377
|
Chris@331
|
378 if (m_runningTransformers.find(transformer) == m_runningTransformers.end()) {
|
Chris@331
|
379 std::cerr << "WARNING: ModelTransformerFactory::transformerFinished("
|
Chris@331
|
380 << transformer
|
Chris@331
|
381 << "): I have no record of this transformer running!"
|
Chris@320
|
382 << std::endl;
|
Chris@320
|
383 }
|
Chris@320
|
384
|
Chris@331
|
385 m_runningTransformers.erase(transformer);
|
Chris@320
|
386
|
Chris@331
|
387 transformer->wait(); // unnecessary but reassuring
|
Chris@331
|
388 delete transformer;
|
Chris@320
|
389 }
|
Chris@320
|
390
|
Chris@320
|
391 void
|
Chris@331
|
392 ModelTransformerFactory::modelAboutToBeDeleted(Model *m)
|
Chris@320
|
393 {
|
Chris@328
|
394 TransformerSet affected;
|
Chris@320
|
395
|
Chris@328
|
396 for (TransformerSet::iterator i = m_runningTransformers.begin();
|
Chris@328
|
397 i != m_runningTransformers.end(); ++i) {
|
Chris@320
|
398
|
Chris@331
|
399 ModelTransformer *t = *i;
|
Chris@320
|
400
|
Chris@320
|
401 if (t->getInputModel() == m || t->getOutputModel() == m) {
|
Chris@320
|
402 affected.insert(t);
|
Chris@320
|
403 }
|
Chris@320
|
404 }
|
Chris@320
|
405
|
Chris@328
|
406 for (TransformerSet::iterator i = affected.begin();
|
Chris@320
|
407 i != affected.end(); ++i) {
|
Chris@320
|
408
|
Chris@331
|
409 ModelTransformer *t = *i;
|
Chris@320
|
410
|
Chris@320
|
411 t->abandon();
|
Chris@320
|
412
|
Chris@320
|
413 t->wait(); // this should eventually call back on
|
Chris@331
|
414 // transformerFinished, which will remove from
|
Chris@328
|
415 // m_runningTransformers and delete.
|
Chris@320
|
416 }
|
Chris@320
|
417 }
|
Chris@320
|
418
|