Mercurial > hg > svapp
comparison audioio/AudioGenerator.cpp @ 328:28c17ce7a6e9 tony_integration
Merge from tonioni branch
author | Chris Cannam |
---|---|
date | Tue, 28 Jan 2014 15:02:15 +0000 |
parents | 5c69d40a0e30 |
children | 0e4332efcc7d |
comparison
equal
deleted
inserted
replaced
304:c837368b1faf | 328:28c17ce7a6e9 |
---|---|
20 #include "base/PlayParameterRepository.h" | 20 #include "base/PlayParameterRepository.h" |
21 #include "base/Pitch.h" | 21 #include "base/Pitch.h" |
22 #include "base/Exceptions.h" | 22 #include "base/Exceptions.h" |
23 | 23 |
24 #include "data/model/NoteModel.h" | 24 #include "data/model/NoteModel.h" |
25 #include "data/model/FlexiNoteModel.h" | |
25 #include "data/model/DenseTimeValueModel.h" | 26 #include "data/model/DenseTimeValueModel.h" |
27 #include "data/model/SparseTimeValueModel.h" | |
26 #include "data/model/SparseOneDimensionalModel.h" | 28 #include "data/model/SparseOneDimensionalModel.h" |
27 | 29 #include "data/model/NoteData.h" |
28 #include "plugin/RealTimePluginFactory.h" | 30 |
29 #include "plugin/RealTimePluginInstance.h" | 31 #include "ClipMixer.h" |
30 #include "plugin/PluginIdentifier.h" | 32 #include "ContinuousSynth.h" |
31 #include "plugin/PluginXml.h" | |
32 #include "plugin/api/alsa/seq_event.h" | |
33 | 33 |
34 #include <iostream> | 34 #include <iostream> |
35 #include <cmath> | 35 #include <cmath> |
36 | 36 |
37 #include <QDir> | 37 #include <QDir> |
38 #include <QFile> | 38 #include <QFile> |
39 | 39 |
40 const size_t | 40 const size_t |
41 AudioGenerator::m_pluginBlockSize = 2048; | 41 AudioGenerator::m_processingBlockSize = 1024; |
42 | 42 |
43 QString | 43 QString |
44 AudioGenerator::m_sampleDir = ""; | 44 AudioGenerator::m_sampleDir = ""; |
45 | 45 |
46 //#define DEBUG_AUDIO_GENERATOR 1 | 46 //#define DEBUG_AUDIO_GENERATOR 1 |
47 | 47 |
48 AudioGenerator::AudioGenerator() : | 48 AudioGenerator::AudioGenerator() : |
49 m_sourceSampleRate(0), | 49 m_sourceSampleRate(0), |
50 m_targetChannelCount(1), | 50 m_targetChannelCount(1), |
51 m_waveType(0), | |
51 m_soloing(false) | 52 m_soloing(false) |
52 { | 53 { |
53 initialiseSampleDir(); | 54 initialiseSampleDir(); |
54 | 55 |
55 connect(PlayParameterRepository::getInstance(), | 56 connect(PlayParameterRepository::getInstance(), |
56 SIGNAL(playPluginIdChanged(const Playable *, QString)), | 57 SIGNAL(playClipIdChanged(const Playable *, QString)), |
57 this, | 58 this, |
58 SLOT(playPluginIdChanged(const Playable *, QString))); | 59 SLOT(playClipIdChanged(const Playable *, QString))); |
59 | |
60 connect(PlayParameterRepository::getInstance(), | |
61 SIGNAL(playPluginConfigurationChanged(const Playable *, QString)), | |
62 this, | |
63 SLOT(playPluginConfigurationChanged(const Playable *, QString))); | |
64 } | 60 } |
65 | 61 |
66 AudioGenerator::~AudioGenerator() | 62 AudioGenerator::~AudioGenerator() |
67 { | 63 { |
68 #ifdef DEBUG_AUDIO_GENERATOR | 64 #ifdef DEBUG_AUDIO_GENERATOR |
123 m_sourceSampleRate = model->getSampleRate(); | 119 m_sourceSampleRate = model->getSampleRate(); |
124 return true; | 120 return true; |
125 } | 121 } |
126 } | 122 } |
127 | 123 |
128 RealTimePluginInstance *plugin = loadPluginFor(model); | 124 if (usesClipMixer(model)) { |
129 if (plugin) { | 125 ClipMixer *mixer = makeClipMixerFor(model); |
130 QMutexLocker locker(&m_mutex); | 126 if (mixer) { |
131 m_synthMap[model] = plugin; | 127 QMutexLocker locker(&m_mutex); |
132 return true; | 128 m_clipMixerMap[model] = mixer; |
129 return true; | |
130 } | |
131 } | |
132 | |
133 if (usesContinuousSynth(model)) { | |
134 ContinuousSynth *synth = makeSynthFor(model); | |
135 if (synth) { | |
136 QMutexLocker locker(&m_mutex); | |
137 m_continuousSynthMap[model] = synth; | |
138 return true; | |
139 } | |
133 } | 140 } |
134 | 141 |
135 return false; | 142 return false; |
136 } | 143 } |
137 | 144 |
138 void | 145 void |
139 AudioGenerator::playPluginIdChanged(const Playable *playable, QString) | 146 AudioGenerator::playClipIdChanged(const Playable *playable, QString) |
140 { | 147 { |
141 const Model *model = dynamic_cast<const Model *>(playable); | 148 const Model *model = dynamic_cast<const Model *>(playable); |
142 if (!model) { | 149 if (!model) { |
143 cerr << "WARNING: AudioGenerator::playPluginIdChanged: playable " | 150 cerr << "WARNING: AudioGenerator::playClipIdChanged: playable " |
144 << playable << " is not a supported model type" | 151 << playable << " is not a supported model type" |
145 << endl; | 152 << endl; |
146 return; | 153 return; |
147 } | 154 } |
148 | 155 |
149 if (m_synthMap.find(model) == m_synthMap.end()) return; | 156 if (m_clipMixerMap.find(model) == m_clipMixerMap.end()) return; |
150 | 157 |
151 RealTimePluginInstance *plugin = loadPluginFor(model); | 158 ClipMixer *mixer = makeClipMixerFor(model); |
152 if (plugin) { | 159 if (mixer) { |
153 QMutexLocker locker(&m_mutex); | 160 QMutexLocker locker(&m_mutex); |
154 delete m_synthMap[model]; | 161 m_clipMixerMap[model] = mixer; |
155 m_synthMap[model] = plugin; | 162 } |
156 } | 163 } |
157 } | 164 |
158 | 165 bool |
159 void | 166 AudioGenerator::usesClipMixer(const Model *model) |
160 AudioGenerator::playPluginConfigurationChanged(const Playable *playable, | 167 { |
161 QString configurationXml) | 168 bool clip = |
162 { | 169 (qobject_cast<const SparseOneDimensionalModel *>(model) || |
163 // SVDEBUG << "AudioGenerator::playPluginConfigurationChanged" << endl; | 170 qobject_cast<const NoteModel *>(model) || |
164 | 171 qobject_cast<const FlexiNoteModel *>(model)); |
165 const Model *model = dynamic_cast<const Model *>(playable); | 172 return clip; |
166 if (!model) { | 173 } |
167 cerr << "WARNING: AudioGenerator::playPluginIdChanged: playable " | 174 |
168 << playable << " is not a supported model type" | 175 bool |
169 << endl; | 176 AudioGenerator::usesContinuousSynth(const Model *model) |
170 return; | 177 { |
171 } | 178 bool cont = |
172 | 179 (qobject_cast<const SparseTimeValueModel *>(model)); |
173 if (m_synthMap.find(model) == m_synthMap.end()) { | 180 return cont; |
174 SVDEBUG << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << endl; | 181 } |
175 return; | 182 |
176 } | 183 ClipMixer * |
177 | 184 AudioGenerator::makeClipMixerFor(const Model *model) |
178 RealTimePluginInstance *plugin = m_synthMap[model]; | 185 { |
179 if (plugin) { | 186 QString clipId; |
180 PluginXml(plugin).setParametersFromXml(configurationXml); | |
181 } | |
182 } | |
183 | |
184 void | |
185 AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) | |
186 { | |
187 if (m_sampleDir != "") { | |
188 plugin->configure("sampledir", m_sampleDir.toStdString()); | |
189 } | |
190 } | |
191 | |
192 RealTimePluginInstance * | |
193 AudioGenerator::loadPluginFor(const Model *model) | |
194 { | |
195 QString pluginId, configurationXml; | |
196 | 187 |
197 const Playable *playable = model; | 188 const Playable *playable = model; |
198 if (!playable || !playable->canPlay()) return 0; | 189 if (!playable || !playable->canPlay()) return 0; |
199 | 190 |
200 PlayParameters *parameters = | 191 PlayParameters *parameters = |
201 PlayParameterRepository::getInstance()->getPlayParameters(playable); | 192 PlayParameterRepository::getInstance()->getPlayParameters(playable); |
202 if (parameters) { | 193 if (parameters) { |
203 pluginId = parameters->getPlayPluginId(); | 194 clipId = parameters->getPlayClipId(); |
204 configurationXml = parameters->getPlayPluginConfiguration(); | 195 } |
205 } | 196 |
206 | 197 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): sample id = " << clipId << std::endl; |
207 if (pluginId == "") return 0; | 198 |
208 | 199 if (clipId == "") { |
209 RealTimePluginInstance *plugin = loadPlugin(pluginId, ""); | 200 SVDEBUG << "AudioGenerator::makeClipMixerFor(" << model << "): no sample, skipping" << endl; |
210 if (!plugin) return 0; | |
211 | |
212 if (configurationXml != "") { | |
213 PluginXml(plugin).setParametersFromXml(configurationXml); | |
214 setSampleDir(plugin); | |
215 } | |
216 | |
217 configurationXml = PluginXml(plugin).toXmlString(); | |
218 | |
219 if (parameters) { | |
220 parameters->setPlayPluginId(pluginId); | |
221 parameters->setPlayPluginConfiguration(configurationXml); | |
222 } | |
223 | |
224 return plugin; | |
225 } | |
226 | |
227 RealTimePluginInstance * | |
228 AudioGenerator::loadPlugin(QString pluginId, QString program) | |
229 { | |
230 RealTimePluginFactory *factory = | |
231 RealTimePluginFactory::instanceFor(pluginId); | |
232 | |
233 if (!factory) { | |
234 cerr << "Failed to get plugin factory" << endl; | |
235 return 0; | |
236 } | |
237 | |
238 RealTimePluginInstance *instance = | |
239 factory->instantiatePlugin | |
240 (pluginId, 0, 0, m_sourceSampleRate, m_pluginBlockSize, m_targetChannelCount); | |
241 | |
242 if (!instance) { | |
243 cerr << "Failed to instantiate plugin " << pluginId << endl; | |
244 return 0; | 201 return 0; |
245 } | 202 } |
246 | 203 |
247 setSampleDir(instance); | 204 ClipMixer *mixer = new ClipMixer(m_targetChannelCount, |
248 | 205 m_sourceSampleRate, |
249 for (unsigned int i = 0; i < instance->getParameterCount(); ++i) { | 206 m_processingBlockSize); |
250 instance->setParameterValue(i, instance->getParameterDefault(i)); | 207 |
251 } | 208 float clipF0 = Pitch::getFrequencyForPitch(60, 0, 440.0f); // required |
252 std::string defaultProgram = instance->getProgram(0, 0); | 209 |
253 if (defaultProgram != "") { | 210 QString clipPath = QString("%1/%2.wav").arg(m_sampleDir).arg(clipId); |
254 // cerr << "first selecting default program " << defaultProgram << endl; | 211 |
255 instance->selectProgram(defaultProgram); | 212 if (!mixer->loadClipData(clipPath, clipF0)) { |
256 } | 213 delete mixer; |
257 if (program != "") { | 214 return 0; |
258 // cerr << "now selecting desired program " << program << endl; | 215 } |
259 instance->selectProgram(program.toStdString()); | 216 |
260 } | 217 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): loaded clip " << clipId << std::endl; |
261 instance->setIdealChannelCount(m_targetChannelCount); // reset! | 218 |
262 | 219 return mixer; |
263 return instance; | 220 } |
221 | |
222 ContinuousSynth * | |
223 AudioGenerator::makeSynthFor(const Model *model) | |
224 { | |
225 const Playable *playable = model; | |
226 if (!playable || !playable->canPlay()) return 0; | |
227 | |
228 ContinuousSynth *synth = new ContinuousSynth(m_targetChannelCount, | |
229 m_sourceSampleRate, | |
230 m_processingBlockSize, | |
231 m_waveType); | |
232 | |
233 std::cerr << "AudioGenerator::makeSynthFor(" << model << "): created synth" << std::endl; | |
234 | |
235 return synth; | |
264 } | 236 } |
265 | 237 |
266 void | 238 void |
267 AudioGenerator::removeModel(Model *model) | 239 AudioGenerator::removeModel(Model *model) |
268 { | 240 { |
270 dynamic_cast<SparseOneDimensionalModel *>(model); | 242 dynamic_cast<SparseOneDimensionalModel *>(model); |
271 if (!sodm) return; // nothing to do | 243 if (!sodm) return; // nothing to do |
272 | 244 |
273 QMutexLocker locker(&m_mutex); | 245 QMutexLocker locker(&m_mutex); |
274 | 246 |
275 if (m_synthMap.find(sodm) == m_synthMap.end()) return; | 247 if (m_clipMixerMap.find(sodm) == m_clipMixerMap.end()) return; |
276 | 248 |
277 RealTimePluginInstance *instance = m_synthMap[sodm]; | 249 ClipMixer *mixer = m_clipMixerMap[sodm]; |
278 m_synthMap.erase(sodm); | 250 m_clipMixerMap.erase(sodm); |
279 delete instance; | 251 delete mixer; |
280 } | 252 } |
281 | 253 |
282 void | 254 void |
283 AudioGenerator::clearModels() | 255 AudioGenerator::clearModels() |
284 { | 256 { |
285 QMutexLocker locker(&m_mutex); | 257 QMutexLocker locker(&m_mutex); |
286 while (!m_synthMap.empty()) { | 258 |
287 RealTimePluginInstance *instance = m_synthMap.begin()->second; | 259 while (!m_clipMixerMap.empty()) { |
288 m_synthMap.erase(m_synthMap.begin()); | 260 ClipMixer *mixer = m_clipMixerMap.begin()->second; |
289 delete instance; | 261 m_clipMixerMap.erase(m_clipMixerMap.begin()); |
262 delete mixer; | |
290 } | 263 } |
291 } | 264 } |
292 | 265 |
293 void | 266 void |
294 AudioGenerator::reset() | 267 AudioGenerator::reset() |
295 { | 268 { |
296 QMutexLocker locker(&m_mutex); | 269 QMutexLocker locker(&m_mutex); |
297 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { | 270 |
271 for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { | |
298 if (i->second) { | 272 if (i->second) { |
299 i->second->silence(); | 273 i->second->reset(); |
300 i->second->discardEvents(); | |
301 } | 274 } |
302 } | 275 } |
303 | 276 |
304 m_noteOffs.clear(); | 277 m_noteOffs.clear(); |
305 } | 278 } |
312 // SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; | 285 // SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; |
313 | 286 |
314 QMutexLocker locker(&m_mutex); | 287 QMutexLocker locker(&m_mutex); |
315 m_targetChannelCount = targetChannelCount; | 288 m_targetChannelCount = targetChannelCount; |
316 | 289 |
317 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { | 290 for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { |
318 if (i->second) i->second->setIdealChannelCount(targetChannelCount); | 291 if (i->second) i->second->setChannelCount(targetChannelCount); |
319 } | 292 } |
320 } | 293 } |
321 | 294 |
322 size_t | 295 size_t |
323 AudioGenerator::getBlockSize() const | 296 AudioGenerator::getBlockSize() const |
324 { | 297 { |
325 return m_pluginBlockSize; | 298 return m_processingBlockSize; |
326 } | 299 } |
327 | 300 |
328 void | 301 void |
329 AudioGenerator::setSoloModelSet(std::set<Model *> s) | 302 AudioGenerator::setSoloModelSet(std::set<Model *> s) |
330 { | 303 { |
385 if (dtvm) { | 358 if (dtvm) { |
386 return mixDenseTimeValueModel(dtvm, startFrame, frameCount, | 359 return mixDenseTimeValueModel(dtvm, startFrame, frameCount, |
387 buffer, gain, pan, fadeIn, fadeOut); | 360 buffer, gain, pan, fadeIn, fadeOut); |
388 } | 361 } |
389 | 362 |
390 bool synthetic = | 363 if (usesClipMixer(model)) { |
391 (qobject_cast<SparseOneDimensionalModel *>(model) || | 364 return mixClipModel(model, startFrame, frameCount, |
392 qobject_cast<NoteModel *>(model)); | 365 buffer, gain, pan); |
393 | 366 } |
394 if (synthetic) { | 367 |
395 return mixSyntheticNoteModel(model, startFrame, frameCount, | 368 if (usesContinuousSynth(model)) { |
396 buffer, gain, pan, fadeIn, fadeOut); | 369 return mixContinuousSynthModel(model, startFrame, frameCount, |
397 } | 370 buffer, gain, pan); |
371 } | |
372 | |
373 std::cerr << "AudioGenerator::mixModel: WARNING: Model " << model << " of type " << model->getTypeName() << " is marked as playable, but I have no mechanism to play it" << std::endl; | |
398 | 374 |
399 return frameCount; | 375 return frameCount; |
400 } | 376 } |
401 | 377 |
402 size_t | 378 size_t |
493 | 469 |
494 return got; | 470 return got; |
495 } | 471 } |
496 | 472 |
497 size_t | 473 size_t |
498 AudioGenerator::mixSyntheticNoteModel(Model *model, | 474 AudioGenerator::mixClipModel(Model *model, |
499 size_t startFrame, size_t frames, | 475 size_t startFrame, size_t frames, |
500 float **buffer, float gain, float pan, | 476 float **buffer, float gain, float pan) |
501 size_t /* fadeIn */, | 477 { |
502 size_t /* fadeOut */) | 478 ClipMixer *clipMixer = m_clipMixerMap[model]; |
503 { | 479 if (!clipMixer) return 0; |
504 RealTimePluginInstance *plugin = m_synthMap[model]; | 480 |
505 if (!plugin) return 0; | 481 size_t blocks = frames / m_processingBlockSize; |
506 | |
507 size_t latency = plugin->getLatency(); | |
508 size_t blocks = frames / m_pluginBlockSize; | |
509 | 482 |
483 //!!! todo: the below -- it matters | |
484 | |
510 //!!! hang on -- the fact that the audio callback play source's | 485 //!!! hang on -- the fact that the audio callback play source's |
511 //buffer is a multiple of the plugin's buffer size doesn't mean | 486 //buffer is a multiple of the plugin's buffer size doesn't mean |
512 //that we always get called for a multiple of it here (because it | 487 //that we always get called for a multiple of it here (because it |
513 //also depends on the JACK block size). how should we ensure that | 488 //also depends on the JACK block size). how should we ensure that |
514 //all models write the same amount in to the mix, and that we | 489 //all models write the same amount in to the mix, and that we |
515 //always have a multiple of the plugin buffer size? I guess this | 490 //always have a multiple of the plugin buffer size? I guess this |
516 //class has to be queryable for the plugin buffer size & the | 491 //class has to be queryable for the plugin buffer size & the |
517 //callback play source has to use that as a multiple for all the | 492 //callback play source has to use that as a multiple for all the |
518 //calls to mixModel | 493 //calls to mixModel |
519 | 494 |
520 size_t got = blocks * m_pluginBlockSize; | 495 size_t got = blocks * m_processingBlockSize; |
521 | 496 |
522 #ifdef DEBUG_AUDIO_GENERATOR | 497 #ifdef DEBUG_AUDIO_GENERATOR |
523 cout << "mixModel [synthetic note]: frames " << frames | 498 cout << "mixModel [clip]: frames " << frames |
524 << ", blocks " << blocks << endl; | 499 << ", blocks " << blocks << endl; |
525 #endif | 500 #endif |
526 | 501 |
527 snd_seq_event_t onEv; | 502 ClipMixer::NoteStart on; |
528 onEv.type = SND_SEQ_EVENT_NOTEON; | 503 ClipMixer::NoteEnd off; |
529 onEv.data.note.channel = 0; | 504 |
530 | |
531 snd_seq_event_t offEv; | |
532 offEv.type = SND_SEQ_EVENT_NOTEOFF; | |
533 offEv.data.note.channel = 0; | |
534 offEv.data.note.velocity = 0; | |
535 | |
536 NoteOffSet ¬eOffs = m_noteOffs[model]; | 505 NoteOffSet ¬eOffs = m_noteOffs[model]; |
537 | 506 |
507 float **bufferIndexes = new float *[m_targetChannelCount]; | |
508 | |
538 for (size_t i = 0; i < blocks; ++i) { | 509 for (size_t i = 0; i < blocks; ++i) { |
539 | 510 |
540 size_t reqStart = startFrame + i * m_pluginBlockSize; | 511 size_t reqStart = startFrame + i * m_processingBlockSize; |
541 | 512 |
542 NoteList notes = getNotes(model, | 513 NoteList notes; |
543 reqStart + latency, | 514 NoteExportable *exportable = dynamic_cast<NoteExportable *>(model); |
544 reqStart + latency + m_pluginBlockSize); | 515 if (exportable) { |
545 | 516 notes = exportable->getNotes(reqStart, |
546 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime | 517 reqStart + m_processingBlockSize); |
547 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); | 518 } |
519 | |
520 std::vector<ClipMixer::NoteStart> starts; | |
521 std::vector<ClipMixer::NoteEnd> ends; | |
548 | 522 |
549 for (NoteList::const_iterator ni = notes.begin(); | 523 for (NoteList::const_iterator ni = notes.begin(); |
550 ni != notes.end(); ++ni) { | 524 ni != notes.end(); ++ni) { |
551 | 525 |
552 size_t noteFrame = ni->start; | 526 size_t noteFrame = ni->start; |
553 | 527 |
554 if (noteFrame >= latency) noteFrame -= latency; | |
555 | |
556 if (noteFrame < reqStart || | 528 if (noteFrame < reqStart || |
557 noteFrame >= reqStart + m_pluginBlockSize) continue; | 529 noteFrame >= reqStart + m_processingBlockSize) continue; |
558 | 530 |
559 while (noteOffs.begin() != noteOffs.end() && | 531 while (noteOffs.begin() != noteOffs.end() && |
560 noteOffs.begin()->frame <= noteFrame) { | 532 noteOffs.begin()->frame <= noteFrame) { |
561 | 533 |
562 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 534 size_t eventFrame = noteOffs.begin()->frame; |
563 (noteOffs.begin()->frame, m_sourceSampleRate); | 535 if (eventFrame < reqStart) eventFrame = reqStart; |
564 | 536 |
565 offEv.data.note.note = noteOffs.begin()->pitch; | 537 off.frameOffset = eventFrame - reqStart; |
566 | 538 off.frequency = noteOffs.begin()->frequency; |
567 #ifdef DEBUG_AUDIO_GENERATOR | 539 |
568 cerr << "mixModel [synthetic]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; | 540 #ifdef DEBUG_AUDIO_GENERATOR |
569 #endif | 541 cerr << "mixModel [clip]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; |
570 | 542 #endif |
571 plugin->sendEvent(eventTime, &offEv); | 543 |
544 ends.push_back(off); | |
572 noteOffs.erase(noteOffs.begin()); | 545 noteOffs.erase(noteOffs.begin()); |
573 } | 546 } |
574 | 547 |
575 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 548 on.frameOffset = noteFrame - reqStart; |
576 (noteFrame, m_sourceSampleRate); | 549 on.frequency = ni->getFrequency(); |
550 on.level = float(ni->velocity) / 127.0; | |
551 on.pan = pan; | |
552 | |
553 #ifdef DEBUG_AUDIO_GENERATOR | |
554 cout << "mixModel [clip]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << endl; | |
555 #endif | |
577 | 556 |
578 if (ni->isMidiPitchQuantized) { | 557 starts.push_back(on); |
579 onEv.data.note.note = ni->midiPitch; | 558 noteOffs.insert |
580 } else { | 559 (NoteOff(on.frequency, noteFrame + ni->duration)); |
581 #ifdef DEBUG_AUDIO_GENERATOR | 560 } |
582 cerr << "mixModel [synthetic]: non-pitch-quantized notes are not supported [yet], quantizing" << endl; | 561 |
583 #endif | 562 while (noteOffs.begin() != noteOffs.end() && |
584 onEv.data.note.note = Pitch::getPitchForFrequency(ni->frequency); | 563 noteOffs.begin()->frame <= reqStart + m_processingBlockSize) { |
564 | |
565 size_t eventFrame = noteOffs.begin()->frame; | |
566 if (eventFrame < reqStart) eventFrame = reqStart; | |
567 | |
568 off.frameOffset = eventFrame - reqStart; | |
569 off.frequency = noteOffs.begin()->frequency; | |
570 | |
571 #ifdef DEBUG_AUDIO_GENERATOR | |
572 cerr << "mixModel [clip]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; | |
573 #endif | |
574 | |
575 ends.push_back(off); | |
576 noteOffs.erase(noteOffs.begin()); | |
577 } | |
578 | |
579 for (size_t c = 0; c < m_targetChannelCount; ++c) { | |
580 bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; | |
581 } | |
582 | |
583 clipMixer->mix(bufferIndexes, gain, starts, ends); | |
584 } | |
585 | |
586 delete[] bufferIndexes; | |
587 | |
588 return got; | |
589 } | |
590 | |
591 size_t | |
592 AudioGenerator::mixContinuousSynthModel(Model *model, | |
593 size_t startFrame, | |
594 size_t frames, | |
595 float **buffer, | |
596 float gain, | |
597 float pan) | |
598 { | |
599 ContinuousSynth *synth = m_continuousSynthMap[model]; | |
600 if (!synth) return 0; | |
601 | |
602 // only type we support here at the moment | |
603 SparseTimeValueModel *stvm = qobject_cast<SparseTimeValueModel *>(model); | |
604 if (stvm->getScaleUnits() != "Hz") return 0; | |
605 | |
606 size_t blocks = frames / m_processingBlockSize; | |
607 | |
608 //!!! todo: see comment in mixClipModel | |
609 | |
610 size_t got = blocks * m_processingBlockSize; | |
611 | |
612 #ifdef DEBUG_AUDIO_GENERATOR | |
613 cout << "mixModel [synth]: frames " << frames | |
614 << ", blocks " << blocks << endl; | |
615 #endif | |
616 | |
617 float **bufferIndexes = new float *[m_targetChannelCount]; | |
618 | |
619 for (size_t i = 0; i < blocks; ++i) { | |
620 | |
621 size_t reqStart = startFrame + i * m_processingBlockSize; | |
622 | |
623 for (size_t c = 0; c < m_targetChannelCount; ++c) { | |
624 bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; | |
625 } | |
626 | |
627 SparseTimeValueModel::PointList points = | |
628 stvm->getPoints(reqStart, reqStart + m_processingBlockSize); | |
629 | |
630 // by default, repeat last frequency | |
631 float f0 = 0.f; | |
632 | |
633 // go straight to the last freq that is genuinely in this range | |
634 for (SparseTimeValueModel::PointList::const_iterator itr = points.end(); | |
635 itr != points.begin(); ) { | |
636 --itr; | |
637 if (itr->frame >= reqStart && | |
638 itr->frame < reqStart + m_processingBlockSize) { | |
639 f0 = itr->value; | |
640 break; | |
585 } | 641 } |
586 | 642 } |
587 onEv.data.note.velocity = ni->velocity; | 643 |
588 | 644 // if we found no such frequency and the next point is further |
589 plugin->sendEvent(eventTime, &onEv); | 645 // away than twice the model resolution, go silent (same |
590 | 646 // criterion TimeValueLayer uses for ending a discrete curve |
591 #ifdef DEBUG_AUDIO_GENERATOR | 647 // segment) |
592 cout << "mixModel [synthetic]: note at frame " << noteFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << endl; | 648 if (f0 == 0.f) { |
593 #endif | 649 SparseTimeValueModel::PointList nextPoints = |
594 | 650 stvm->getNextPoints(reqStart + m_processingBlockSize); |
595 noteOffs.insert | 651 if (nextPoints.empty() || |
596 (NoteOff(onEv.data.note.note, noteFrame + ni->duration)); | 652 nextPoints.begin()->frame > reqStart + 2 * stvm->getResolution()) { |
597 } | 653 f0 = -1.f; |
598 | 654 } |
599 while (noteOffs.begin() != noteOffs.end() && | 655 } |
600 noteOffs.begin()->frame <= | 656 |
601 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { | 657 // cerr << "f0 = " << f0 << endl; |
602 | 658 |
603 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 659 synth->mix(bufferIndexes, |
604 (noteOffs.begin()->frame, m_sourceSampleRate); | 660 gain, |
605 | 661 pan, |
606 offEv.data.note.note = noteOffs.begin()->pitch; | 662 f0); |
607 | 663 } |
608 #ifdef DEBUG_AUDIO_GENERATOR | 664 |
609 cerr << "mixModel [synthetic]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; | 665 delete[] bufferIndexes; |
610 #endif | |
611 | |
612 plugin->sendEvent(eventTime, &offEv); | |
613 noteOffs.erase(noteOffs.begin()); | |
614 } | |
615 | |
616 plugin->run(blockTime); | |
617 float **outs = plugin->getAudioOutputBuffers(); | |
618 | |
619 for (size_t c = 0; c < m_targetChannelCount; ++c) { | |
620 #ifdef DEBUG_AUDIO_GENERATOR | |
621 cout << "mixModel [synthetic]: adding " << m_pluginBlockSize << " samples from plugin output " << c << endl; | |
622 #endif | |
623 | |
624 size_t sourceChannel = (c % plugin->getAudioOutputCount()); | |
625 | |
626 float channelGain = gain; | |
627 if (pan != 0.0) { | |
628 if (c == 0) { | |
629 if (pan > 0.0) channelGain *= 1.0 - pan; | |
630 } else { | |
631 if (pan < 0.0) channelGain *= pan + 1.0; | |
632 } | |
633 } | |
634 | |
635 for (size_t j = 0; j < m_pluginBlockSize; ++j) { | |
636 buffer[c][i * m_pluginBlockSize + j] += | |
637 channelGain * outs[sourceChannel][j]; | |
638 } | |
639 } | |
640 } | |
641 | 666 |
642 return got; | 667 return got; |
643 } | 668 } |
644 | 669 |
645 AudioGenerator::NoteList | |
646 AudioGenerator::getNotes(Model *model, | |
647 size_t startFrame, | |
648 size_t endFrame) | |
649 { | |
650 NoteList notes; | |
651 | |
652 SparseOneDimensionalModel *sodm = | |
653 qobject_cast<SparseOneDimensionalModel *>(model); | |
654 | |
655 if (sodm) { | |
656 | |
657 SparseOneDimensionalModel::PointList points = | |
658 sodm->getPoints(startFrame, endFrame); | |
659 | |
660 for (SparseOneDimensionalModel::PointList::iterator pli = | |
661 points.begin(); pli != points.end(); ++pli) { | |
662 | |
663 notes.push_back | |
664 (NoteData(pli->frame, | |
665 m_sourceSampleRate / 6, // arbitrary short duration | |
666 64, // default pitch | |
667 100)); // default velocity | |
668 } | |
669 | |
670 return notes; | |
671 } | |
672 | |
673 NoteModel *nm = qobject_cast<NoteModel *>(model); | |
674 | |
675 if (nm) { | |
676 | |
677 NoteModel::PointList points = | |
678 nm->getPoints(startFrame, endFrame); | |
679 | |
680 for (NoteModel::PointList::iterator pli = | |
681 points.begin(); pli != points.end(); ++pli) { | |
682 | |
683 size_t duration = pli->duration; | |
684 if (duration == 0 || duration == 1) { | |
685 duration = m_sourceSampleRate / 20; | |
686 } | |
687 | |
688 int pitch = lrintf(pli->value); | |
689 | |
690 int velocity = 100; | |
691 if (pli->level > 0.f && pli->level <= 1.f) { | |
692 velocity = lrintf(pli->level * 127); | |
693 } | |
694 | |
695 NoteData note(pli->frame, | |
696 duration, | |
697 pitch, | |
698 velocity); | |
699 | |
700 if (nm->getScaleUnits() == "Hz") { | |
701 note.frequency = pli->value; | |
702 note.isMidiPitchQuantized = false; | |
703 } | |
704 | |
705 notes.push_back(note); | |
706 } | |
707 | |
708 return notes; | |
709 } | |
710 | |
711 return notes; | |
712 } | |
713 |