Mercurial > hg > svapp
comparison audioio/AudioGenerator.cpp @ 308:289d65722123 tonioni
More work on mixing and file i/o for sample stuff
author | Chris Cannam |
---|---|
date | Tue, 07 Jan 2014 15:50:04 +0000 |
parents | 6eb15c3aee0a |
children | 71050ffd0141 |
comparison
equal
deleted
inserted
replaced
307:6eb15c3aee0a | 308:289d65722123 |
---|---|
143 | 143 |
144 ClipMixer *mixer = makeClipMixerFor(model); | 144 ClipMixer *mixer = makeClipMixerFor(model); |
145 if (mixer) { | 145 if (mixer) { |
146 QMutexLocker locker(&m_mutex); | 146 QMutexLocker locker(&m_mutex); |
147 m_clipMixerMap[model] = mixer; | 147 m_clipMixerMap[model] = mixer; |
148 return true; | 148 } |
149 } | 149 } |
150 } | 150 |
151 /*!!! | 151 /*!!! |
152 void | 152 void |
153 AudioGenerator::playPluginConfigurationChanged(const Playable *playable, | 153 AudioGenerator::playPluginConfigurationChanged(const Playable *playable, |
154 QString configurationXml) | 154 QString configurationXml) |
155 { | 155 { |
194 PlayParameterRepository::getInstance()->getPlayParameters(playable); | 194 PlayParameterRepository::getInstance()->getPlayParameters(playable); |
195 if (parameters) { | 195 if (parameters) { |
196 sampleId = parameters->getPlaySampleId(); | 196 sampleId = parameters->getPlaySampleId(); |
197 } | 197 } |
198 | 198 |
199 std::cerr << "AudioGenerator::loadPluginFor(" << model << "): sample id = " << sampleId << std::endl; | 199 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): sample id = " << sampleId << std::endl; |
200 | 200 |
201 if (sampleId == "") { | 201 if (sampleId == "") { |
202 SVDEBUG << "AudioGenerator::loadPluginFor(" << model << "): no sample, skipping" << endl; | 202 SVDEBUG << "AudioGenerator::makeClipMixerFor(" << model << "): no sample, skipping" << endl; |
203 return 0; | 203 return 0; |
204 } | 204 } |
205 | 205 |
206 | 206 ClipMixer *mixer = new ClipMixer(m_targetChannelCount, |
207 | 207 m_sourceSampleRate, |
208 | 208 m_processingBlockSize); |
209 | 209 |
210 | 210 float clipF0 = Pitch::getFrequencyForPitch(60, 0, 440.0f); // required |
211 RealTimePluginInstance *plugin = loadPlugin(pluginId, ""); | 211 |
212 if (!plugin) return 0; | 212 QString clipPath = QString("%1/%2.wav").arg(m_sampleDir).arg(sampleId); |
213 | 213 |
214 std::cerr << "AudioGenerator::loadPluginFor(" << model << "): loaded plugin " | 214 if (!mixer->loadClipData(clipPath, clipF0)) { |
215 << plugin << std::endl; | 215 delete mixer; |
216 | |
217 if (configurationXml != "") { | |
218 PluginXml(plugin).setParametersFromXml(configurationXml); | |
219 setSampleDir(plugin); | |
220 } | |
221 | |
222 configurationXml = PluginXml(plugin).toXmlString(); | |
223 | |
224 if (parameters) { | |
225 parameters->setPlaySampleId(pluginId); | |
226 parameters->setPlayPluginConfiguration(configurationXml); | |
227 } | |
228 | |
229 return plugin; | |
230 } | |
231 | |
232 RealTimePluginInstance * | |
233 AudioGenerator::loadPlugin(QString pluginId, QString program) | |
234 { | |
235 RealTimePluginFactory *factory = | |
236 RealTimePluginFactory::instanceFor(pluginId); | |
237 | |
238 if (!factory) { | |
239 cerr << "Failed to get plugin factory" << endl; | |
240 return 0; | |
241 } | |
242 | |
243 RealTimePluginInstance *instance = | |
244 factory->instantiatePlugin | |
245 (pluginId, 0, 0, m_sourceSampleRate, m_processingBlockSize, m_targetChannelCount); | |
246 | |
247 if (!instance) { | |
248 cerr << "Failed to instantiate plugin " << pluginId << endl; | |
249 return 0; | 216 return 0; |
250 } | 217 } |
251 | 218 |
252 setSampleDir(instance); | 219 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): loaded clip " << sampleId << std::endl; |
253 | 220 |
254 for (unsigned int i = 0; i < instance->getParameterCount(); ++i) { | 221 return mixer; |
255 instance->setParameterValue(i, instance->getParameterDefault(i)); | 222 } |
256 } | 223 |
257 std::string defaultProgram = instance->getProgram(0, 0); | |
258 if (defaultProgram != "") { | |
259 cerr << "first selecting default program " << defaultProgram << endl; | |
260 instance->selectProgram(defaultProgram); | |
261 } | |
262 if (program != "") { | |
263 cerr << "now selecting desired program " << program << endl; | |
264 instance->selectProgram(program.toStdString()); | |
265 } | |
266 instance->setIdealChannelCount(m_targetChannelCount); // reset! | |
267 | |
268 return instance; | |
269 } | |
270 */ | |
271 void | 224 void |
272 AudioGenerator::removeModel(Model *model) | 225 AudioGenerator::removeModel(Model *model) |
273 { | 226 { |
274 SparseOneDimensionalModel *sodm = | 227 SparseOneDimensionalModel *sodm = |
275 dynamic_cast<SparseOneDimensionalModel *>(model); | 228 dynamic_cast<SparseOneDimensionalModel *>(model); |
276 if (!sodm) return; // nothing to do | 229 if (!sodm) return; // nothing to do |
277 | 230 |
278 QMutexLocker locker(&m_mutex); | 231 QMutexLocker locker(&m_mutex); |
279 | 232 |
280 if (m_synthMap.find(sodm) == m_synthMap.end()) return; | 233 if (m_clipMixerMap.find(sodm) == m_clipMixerMap.end()) return; |
281 | 234 |
282 //!!! RealTimePluginInstance *instance = m_synthMap[sodm]; | 235 ClipMixer *mixer = m_clipMixerMap[sodm]; |
283 // m_synthMap.erase(sodm); | 236 m_clipMixerMap.erase(sodm); |
284 delete instance; | 237 delete mixer; |
285 } | 238 } |
286 | 239 |
287 void | 240 void |
288 AudioGenerator::clearModels() | 241 AudioGenerator::clearModels() |
289 { | 242 { |
290 QMutexLocker locker(&m_mutex); | 243 QMutexLocker locker(&m_mutex); |
291 /*!!! | 244 |
292 while (!m_synthMap.empty()) { | 245 while (!m_clipMixerMap.empty()) { |
293 RealTimePluginInstance *instance = m_synthMap.begin()->second; | 246 ClipMixer *mixer = m_clipMixerMap.begin()->second; |
294 m_synthMap.erase(m_synthMap.begin()); | 247 m_clipMixerMap.erase(m_clipMixerMap.begin()); |
295 delete instance; | 248 delete mixer; |
296 } | 249 } |
297 */ | |
298 } | 250 } |
299 | 251 |
300 void | 252 void |
301 AudioGenerator::reset() | 253 AudioGenerator::reset() |
302 { | 254 { |
303 QMutexLocker locker(&m_mutex); | 255 QMutexLocker locker(&m_mutex); |
304 /*!!! | 256 |
305 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { | 257 for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { |
306 if (i->second) { | 258 if (i->second) { |
307 i->second->silence(); | 259 i->second->reset(); |
308 i->second->discardEvents(); | 260 } |
309 } | 261 } |
310 } | |
311 */ | |
312 | 262 |
313 m_noteOffs.clear(); | 263 m_noteOffs.clear(); |
314 } | 264 } |
315 | 265 |
316 void | 266 void |
321 // SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; | 271 // SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; |
322 | 272 |
323 QMutexLocker locker(&m_mutex); | 273 QMutexLocker locker(&m_mutex); |
324 m_targetChannelCount = targetChannelCount; | 274 m_targetChannelCount = targetChannelCount; |
325 | 275 |
326 /*!!! | 276 for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { |
327 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { | 277 if (i->second) i->second->setChannelCount(targetChannelCount); |
328 if (i->second) i->second->setIdealChannelCount(targetChannelCount); | 278 } |
329 } | |
330 */ | |
331 } | 279 } |
332 | 280 |
333 size_t | 281 size_t |
334 AudioGenerator::getBlockSize() const | 282 AudioGenerator::getBlockSize() const |
335 { | 283 { |
513 size_t startFrame, size_t frames, | 461 size_t startFrame, size_t frames, |
514 float **buffer, float gain, float pan, | 462 float **buffer, float gain, float pan, |
515 size_t /* fadeIn */, | 463 size_t /* fadeIn */, |
516 size_t /* fadeOut */) | 464 size_t /* fadeOut */) |
517 { | 465 { |
518 RealTimePluginInstance *plugin = m_synthMap[model]; | 466 ClipMixer *clipMixer = m_clipMixerMap[model]; |
519 if (!plugin) return 0; | 467 if (!clipMixer) return 0; |
520 | 468 |
521 size_t latency = plugin->getLatency(); | |
522 size_t blocks = frames / m_processingBlockSize; | 469 size_t blocks = frames / m_processingBlockSize; |
523 | 470 |
524 //!!! hang on -- the fact that the audio callback play source's | 471 //!!! hang on -- the fact that the audio callback play source's |
525 //buffer is a multiple of the plugin's buffer size doesn't mean | 472 //buffer is a multiple of the plugin's buffer size doesn't mean |
526 //that we always get called for a multiple of it here (because it | 473 //that we always get called for a multiple of it here (because it |
536 #ifdef DEBUG_AUDIO_GENERATOR | 483 #ifdef DEBUG_AUDIO_GENERATOR |
537 cout << "mixModel [synthetic note]: frames " << frames | 484 cout << "mixModel [synthetic note]: frames " << frames |
538 << ", blocks " << blocks << endl; | 485 << ", blocks " << blocks << endl; |
539 #endif | 486 #endif |
540 | 487 |
541 snd_seq_event_t onEv; | 488 ClipMixer::NoteStart on; |
542 onEv.type = SND_SEQ_EVENT_NOTEON; | 489 ClipMixer::NoteEnd off; |
543 onEv.data.note.channel = 0; | 490 |
544 | |
545 snd_seq_event_t offEv; | |
546 offEv.type = SND_SEQ_EVENT_NOTEOFF; | |
547 offEv.data.note.channel = 0; | |
548 offEv.data.note.velocity = 0; | |
549 | |
550 NoteOffSet ¬eOffs = m_noteOffs[model]; | 491 NoteOffSet ¬eOffs = m_noteOffs[model]; |
492 | |
493 float **bufferIndexes = new float *[m_targetChannelCount]; | |
551 | 494 |
552 for (size_t i = 0; i < blocks; ++i) { | 495 for (size_t i = 0; i < blocks; ++i) { |
553 | 496 |
554 size_t reqStart = startFrame + i * m_processingBlockSize; | 497 size_t reqStart = startFrame + i * m_processingBlockSize; |
555 | 498 |
556 NoteList notes; | 499 NoteList notes; |
557 NoteExportable *exportable = dynamic_cast<NoteExportable *>(model); | 500 NoteExportable *exportable = dynamic_cast<NoteExportable *>(model); |
558 if (exportable) { | 501 if (exportable) { |
559 notes = exportable->getNotes(reqStart + latency, | 502 notes = exportable->getNotes(reqStart, |
560 reqStart + latency + m_processingBlockSize); | 503 reqStart + m_processingBlockSize); |
561 } | 504 } |
562 | 505 |
563 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime | 506 std::vector<ClipMixer::NoteStart> starts; |
564 (startFrame + i * m_processingBlockSize, m_sourceSampleRate); | 507 std::vector<ClipMixer::NoteEnd> ends; |
565 | 508 |
566 for (NoteList::const_iterator ni = notes.begin(); | 509 for (NoteList::const_iterator ni = notes.begin(); |
567 ni != notes.end(); ++ni) { | 510 ni != notes.end(); ++ni) { |
568 | 511 |
569 size_t noteFrame = ni->start; | 512 size_t noteFrame = ni->start; |
570 | 513 |
571 if (noteFrame >= latency) noteFrame -= latency; | |
572 | |
573 if (noteFrame < reqStart || | 514 if (noteFrame < reqStart || |
574 noteFrame >= reqStart + m_processingBlockSize) continue; | 515 noteFrame >= reqStart + m_processingBlockSize) continue; |
575 | 516 |
576 while (noteOffs.begin() != noteOffs.end() && | 517 while (noteOffs.begin() != noteOffs.end() && |
577 noteOffs.begin()->frame <= noteFrame) { | 518 noteOffs.begin()->frame <= noteFrame) { |
578 | 519 |
579 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 520 size_t eventFrame = noteOffs.begin()->frame; |
580 (noteOffs.begin()->frame, m_sourceSampleRate); | 521 if (eventFrame < reqStart) eventFrame = reqStart; |
581 | 522 |
582 offEv.data.note.note = noteOffs.begin()->pitch; | 523 off.frameOffset = eventFrame - reqStart; |
583 | 524 off.frequency = noteOffs.begin()->frequency; |
584 #ifdef DEBUG_AUDIO_GENERATOR | 525 |
585 cerr << "mixModel [synthetic]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; | 526 #ifdef DEBUG_AUDIO_GENERATOR |
586 #endif | 527 cerr << "mixModel [synthetic]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; |
587 | 528 #endif |
588 plugin->sendEvent(eventTime, &offEv); | 529 |
530 ends.push_back(off); | |
589 noteOffs.erase(noteOffs.begin()); | 531 noteOffs.erase(noteOffs.begin()); |
590 } | 532 } |
591 | 533 |
592 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 534 on.frameOffset = noteFrame - reqStart; |
593 (noteFrame, m_sourceSampleRate); | 535 on.frequency = ni->getFrequency(); |
536 on.level = float(ni->velocity) / 127.0; | |
537 on.pan = pan; | |
538 | |
539 #ifdef DEBUG_AUDIO_GENERATOR | |
540 cout << "mixModel [synthetic]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << endl; | |
541 #endif | |
594 | 542 |
595 if (ni->isMidiPitchQuantized) { | 543 starts.push_back(on); |
596 onEv.data.note.note = ni->midiPitch; | |
597 } else { | |
598 #ifdef DEBUG_AUDIO_GENERATOR | |
599 cerr << "mixModel [synthetic]: non-pitch-quantized notes are not supported [yet], quantizing" << endl; | |
600 #endif | |
601 onEv.data.note.note = Pitch::getPitchForFrequency(ni->frequency); | |
602 } | |
603 | |
604 onEv.data.note.velocity = ni->velocity; | |
605 | |
606 plugin->sendEvent(eventTime, &onEv); | |
607 | |
608 #ifdef DEBUG_AUDIO_GENERATOR | |
609 cout << "mixModel [synthetic]: note at frame " << noteFrame << ", block start " << (startFrame + i * m_processingBlockSize) << ", resulting time " << eventTime << endl; | |
610 #endif | |
611 | |
612 noteOffs.insert | 544 noteOffs.insert |
613 (NoteOff(onEv.data.note.note, noteFrame + ni->duration)); | 545 (NoteOff(on.frequency, noteFrame + ni->duration)); |
614 } | 546 } |
615 | 547 |
616 while (noteOffs.begin() != noteOffs.end() && | 548 while (noteOffs.begin() != noteOffs.end() && |
617 noteOffs.begin()->frame <= | 549 noteOffs.begin()->frame <= reqStart + m_processingBlockSize) { |
618 startFrame + i * m_processingBlockSize + m_processingBlockSize) { | 550 |
619 | 551 size_t eventFrame = noteOffs.begin()->frame; |
620 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 552 if (eventFrame < reqStart) eventFrame = reqStart; |
621 (noteOffs.begin()->frame, m_sourceSampleRate); | 553 |
622 | 554 off.frameOffset = eventFrame - reqStart; |
623 offEv.data.note.note = noteOffs.begin()->pitch; | 555 off.frequency = noteOffs.begin()->frequency; |
624 | 556 |
625 #ifdef DEBUG_AUDIO_GENERATOR | 557 #ifdef DEBUG_AUDIO_GENERATOR |
626 cerr << "mixModel [synthetic]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; | 558 cerr << "mixModel [synthetic]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; |
627 #endif | 559 #endif |
628 | 560 |
629 plugin->sendEvent(eventTime, &offEv); | 561 ends.push_back(off); |
630 noteOffs.erase(noteOffs.begin()); | 562 noteOffs.erase(noteOffs.begin()); |
631 } | 563 } |
632 | |
633 plugin->run(blockTime); | |
634 float **outs = plugin->getAudioOutputBuffers(); | |
635 | 564 |
636 for (size_t c = 0; c < m_targetChannelCount; ++c) { | 565 for (size_t c = 0; c < m_targetChannelCount; ++c) { |
637 #ifdef DEBUG_AUDIO_GENERATOR | 566 bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; |
638 cout << "mixModel [synthetic]: adding " << m_processingBlockSize << " samples from plugin output " << c << endl; | 567 } |
639 #endif | 568 |
640 | 569 clipMixer->mix(bufferIndexes, gain, starts, ends); |
641 size_t sourceChannel = (c % plugin->getAudioOutputCount()); | 570 } |
642 | 571 |
643 float channelGain = gain; | 572 delete[] bufferIndexes; |
644 if (pan != 0.0) { | |
645 if (c == 0) { | |
646 if (pan > 0.0) channelGain *= 1.0 - pan; | |
647 } else { | |
648 if (pan < 0.0) channelGain *= pan + 1.0; | |
649 } | |
650 } | |
651 | |
652 for (size_t j = 0; j < m_processingBlockSize; ++j) { | |
653 buffer[c][i * m_processingBlockSize + j] += | |
654 channelGain * outs[sourceChannel][j]; | |
655 } | |
656 } | |
657 } | |
658 | 573 |
659 return got; | 574 return got; |
660 } | 575 } |