Mercurial > hg > svapp
comparison audioio/AudioGenerator.cpp @ 313:58582119c92a tonioni
Add a basic continuous synth implementation (simple sinusoids only, no gaps)
author | Chris Cannam |
---|---|
date | Wed, 08 Jan 2014 13:07:22 +0000 |
parents | 71050ffd0141 |
children | 817ad10f91d1 |
comparison
equal
deleted
inserted
replaced
312:faee60602049 | 313:58582119c92a |
---|---|
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/FlexiNoteModel.h" |
26 #include "data/model/DenseTimeValueModel.h" | 26 #include "data/model/DenseTimeValueModel.h" |
27 #include "data/model/SparseTimeValueModel.h" | |
27 #include "data/model/SparseOneDimensionalModel.h" | 28 #include "data/model/SparseOneDimensionalModel.h" |
28 #include "data/model/NoteData.h" | 29 #include "data/model/NoteData.h" |
29 | 30 |
30 #include "ClipMixer.h" | 31 #include "ClipMixer.h" |
32 #include "ContinuousSynth.h" | |
31 | 33 |
32 #include <iostream> | 34 #include <iostream> |
33 #include <cmath> | 35 #include <cmath> |
34 | 36 |
35 #include <QDir> | 37 #include <QDir> |
116 m_sourceSampleRate = model->getSampleRate(); | 118 m_sourceSampleRate = model->getSampleRate(); |
117 return true; | 119 return true; |
118 } | 120 } |
119 } | 121 } |
120 | 122 |
121 ClipMixer *mixer = makeClipMixerFor(model); | 123 if (usesClipMixer(model)) { |
122 if (mixer) { | 124 ClipMixer *mixer = makeClipMixerFor(model); |
123 QMutexLocker locker(&m_mutex); | 125 if (mixer) { |
124 m_clipMixerMap[model] = mixer; | 126 QMutexLocker locker(&m_mutex); |
125 return true; | 127 m_clipMixerMap[model] = mixer; |
128 return true; | |
129 } | |
130 } | |
131 | |
132 if (usesContinuousSynth(model)) { | |
133 ContinuousSynth *synth = makeSynthFor(model); | |
134 if (synth) { | |
135 QMutexLocker locker(&m_mutex); | |
136 m_continuousSynthMap[model] = synth; | |
137 return true; | |
138 } | |
126 } | 139 } |
127 | 140 |
128 return false; | 141 return false; |
129 } | 142 } |
130 | 143 |
146 QMutexLocker locker(&m_mutex); | 159 QMutexLocker locker(&m_mutex); |
147 m_clipMixerMap[model] = mixer; | 160 m_clipMixerMap[model] = mixer; |
148 } | 161 } |
149 } | 162 } |
150 | 163 |
151 /*!!! | 164 bool |
152 void | 165 AudioGenerator::usesClipMixer(const Model *model) |
153 AudioGenerator::playPluginConfigurationChanged(const Playable *playable, | 166 { |
154 QString configurationXml) | 167 bool clip = |
155 { | 168 (qobject_cast<const SparseOneDimensionalModel *>(model) || |
156 // SVDEBUG << "AudioGenerator::playPluginConfigurationChanged" << endl; | 169 qobject_cast<const NoteModel *>(model) || |
157 | 170 qobject_cast<const FlexiNoteModel *>(model)); |
158 const Model *model = dynamic_cast<const Model *>(playable); | 171 return clip; |
159 if (!model) { | 172 } |
160 cerr << "WARNING: AudioGenerator::playClipIdChanged: playable " | 173 |
161 << playable << " is not a supported model type" | 174 bool |
162 << endl; | 175 AudioGenerator::usesContinuousSynth(const Model *model) |
163 return; | 176 { |
164 } | 177 bool cont = |
165 | 178 (qobject_cast<const SparseTimeValueModel *>(model)); |
166 if (m_synthMap.find(model) == m_synthMap.end()) { | 179 return cont; |
167 SVDEBUG << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << endl; | 180 } |
168 return; | 181 |
169 } | |
170 | |
171 RealTimePluginInstance *plugin = m_synthMap[model]; | |
172 if (plugin) { | |
173 PluginXml(plugin).setParametersFromXml(configurationXml); | |
174 } | |
175 } | |
176 | |
177 void | |
178 AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) | |
179 { | |
180 if (m_sampleDir != "") { | |
181 plugin->configure("sampledir", m_sampleDir.toStdString()); | |
182 } | |
183 } | |
184 */ | |
185 ClipMixer * | 182 ClipMixer * |
186 AudioGenerator::makeClipMixerFor(const Model *model) | 183 AudioGenerator::makeClipMixerFor(const Model *model) |
187 { | 184 { |
188 QString clipId; | 185 QString clipId; |
189 | 186 |
219 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): loaded clip " << clipId << std::endl; | 216 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): loaded clip " << clipId << std::endl; |
220 | 217 |
221 return mixer; | 218 return mixer; |
222 } | 219 } |
223 | 220 |
221 ContinuousSynth * | |
222 AudioGenerator::makeSynthFor(const Model *model) | |
223 { | |
224 const Playable *playable = model; | |
225 if (!playable || !playable->canPlay()) return 0; | |
226 | |
227 ContinuousSynth *synth = new ContinuousSynth(m_targetChannelCount, | |
228 m_sourceSampleRate, | |
229 m_processingBlockSize); | |
230 | |
231 std::cerr << "AudioGenerator::makeSynthFor(" << model << "): created synth" << std::endl; | |
232 | |
233 return synth; | |
234 } | |
235 | |
224 void | 236 void |
225 AudioGenerator::removeModel(Model *model) | 237 AudioGenerator::removeModel(Model *model) |
226 { | 238 { |
227 SparseOneDimensionalModel *sodm = | 239 SparseOneDimensionalModel *sodm = |
228 dynamic_cast<SparseOneDimensionalModel *>(model); | 240 dynamic_cast<SparseOneDimensionalModel *>(model); |
344 if (dtvm) { | 356 if (dtvm) { |
345 return mixDenseTimeValueModel(dtvm, startFrame, frameCount, | 357 return mixDenseTimeValueModel(dtvm, startFrame, frameCount, |
346 buffer, gain, pan, fadeIn, fadeOut); | 358 buffer, gain, pan, fadeIn, fadeOut); |
347 } | 359 } |
348 | 360 |
349 bool synthetic = | 361 if (usesClipMixer(model)) { |
350 (qobject_cast<SparseOneDimensionalModel *>(model) || | 362 return mixClipModel(model, startFrame, frameCount, |
351 qobject_cast<NoteModel *>(model) || | 363 buffer, gain, pan); |
352 qobject_cast<FlexiNoteModel *>(model)); | 364 } |
353 | 365 |
354 if (synthetic) { | 366 if (usesContinuousSynth(model)) { |
355 return mixSyntheticNoteModel(model, startFrame, frameCount, | 367 return mixContinuousSynthModel(model, startFrame, frameCount, |
356 buffer, gain, pan, fadeIn, fadeOut); | 368 buffer, gain, pan); |
357 } | 369 } |
358 | 370 |
359 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; | 371 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; |
360 | 372 |
361 return frameCount; | 373 return frameCount; |
455 | 467 |
456 return got; | 468 return got; |
457 } | 469 } |
458 | 470 |
459 size_t | 471 size_t |
460 AudioGenerator::mixSyntheticNoteModel(Model *model, | 472 AudioGenerator::mixClipModel(Model *model, |
461 size_t startFrame, size_t frames, | 473 size_t startFrame, size_t frames, |
462 float **buffer, float gain, float pan, | 474 float **buffer, float gain, float pan) |
463 size_t /* fadeIn */, | |
464 size_t /* fadeOut */) | |
465 { | 475 { |
466 ClipMixer *clipMixer = m_clipMixerMap[model]; | 476 ClipMixer *clipMixer = m_clipMixerMap[model]; |
467 if (!clipMixer) return 0; | 477 if (!clipMixer) return 0; |
468 | 478 |
469 size_t blocks = frames / m_processingBlockSize; | 479 size_t blocks = frames / m_processingBlockSize; |
470 | 480 |
481 //!!! todo: the below -- it matters | |
482 | |
471 //!!! hang on -- the fact that the audio callback play source's | 483 //!!! hang on -- the fact that the audio callback play source's |
472 //buffer is a multiple of the plugin's buffer size doesn't mean | 484 //buffer is a multiple of the plugin's buffer size doesn't mean |
473 //that we always get called for a multiple of it here (because it | 485 //that we always get called for a multiple of it here (because it |
474 //also depends on the JACK block size). how should we ensure that | 486 //also depends on the JACK block size). how should we ensure that |
475 //all models write the same amount in to the mix, and that we | 487 //all models write the same amount in to the mix, and that we |
479 //calls to mixModel | 491 //calls to mixModel |
480 | 492 |
481 size_t got = blocks * m_processingBlockSize; | 493 size_t got = blocks * m_processingBlockSize; |
482 | 494 |
483 #ifdef DEBUG_AUDIO_GENERATOR | 495 #ifdef DEBUG_AUDIO_GENERATOR |
484 cout << "mixModel [synthetic note]: frames " << frames | 496 cout << "mixModel [clip]: frames " << frames |
485 << ", blocks " << blocks << endl; | 497 << ", blocks " << blocks << endl; |
486 #endif | 498 #endif |
487 | 499 |
488 ClipMixer::NoteStart on; | 500 ClipMixer::NoteStart on; |
489 ClipMixer::NoteEnd off; | 501 ClipMixer::NoteEnd off; |
522 | 534 |
523 off.frameOffset = eventFrame - reqStart; | 535 off.frameOffset = eventFrame - reqStart; |
524 off.frequency = noteOffs.begin()->frequency; | 536 off.frequency = noteOffs.begin()->frequency; |
525 | 537 |
526 #ifdef DEBUG_AUDIO_GENERATOR | 538 #ifdef DEBUG_AUDIO_GENERATOR |
527 cerr << "mixModel [synthetic]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; | 539 cerr << "mixModel [clip]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; |
528 #endif | 540 #endif |
529 | 541 |
530 ends.push_back(off); | 542 ends.push_back(off); |
531 noteOffs.erase(noteOffs.begin()); | 543 noteOffs.erase(noteOffs.begin()); |
532 } | 544 } |
535 on.frequency = ni->getFrequency(); | 547 on.frequency = ni->getFrequency(); |
536 on.level = float(ni->velocity) / 127.0; | 548 on.level = float(ni->velocity) / 127.0; |
537 on.pan = pan; | 549 on.pan = pan; |
538 | 550 |
539 #ifdef DEBUG_AUDIO_GENERATOR | 551 #ifdef DEBUG_AUDIO_GENERATOR |
540 cout << "mixModel [synthetic]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << endl; | 552 cout << "mixModel [clip]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << endl; |
541 #endif | 553 #endif |
542 | 554 |
543 starts.push_back(on); | 555 starts.push_back(on); |
544 noteOffs.insert | 556 noteOffs.insert |
545 (NoteOff(on.frequency, noteFrame + ni->duration)); | 557 (NoteOff(on.frequency, noteFrame + ni->duration)); |
553 | 565 |
554 off.frameOffset = eventFrame - reqStart; | 566 off.frameOffset = eventFrame - reqStart; |
555 off.frequency = noteOffs.begin()->frequency; | 567 off.frequency = noteOffs.begin()->frequency; |
556 | 568 |
557 #ifdef DEBUG_AUDIO_GENERATOR | 569 #ifdef DEBUG_AUDIO_GENERATOR |
558 cerr << "mixModel [synthetic]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; | 570 cerr << "mixModel [clip]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; |
559 #endif | 571 #endif |
560 | 572 |
561 ends.push_back(off); | 573 ends.push_back(off); |
562 noteOffs.erase(noteOffs.begin()); | 574 noteOffs.erase(noteOffs.begin()); |
563 } | 575 } |
571 | 583 |
572 delete[] bufferIndexes; | 584 delete[] bufferIndexes; |
573 | 585 |
574 return got; | 586 return got; |
575 } | 587 } |
588 | |
589 size_t | |
590 AudioGenerator::mixContinuousSynthModel(Model *model, | |
591 size_t startFrame, | |
592 size_t frames, | |
593 float **buffer, | |
594 float gain, | |
595 float pan) | |
596 { | |
597 ContinuousSynth *synth = m_continuousSynthMap[model]; | |
598 if (!synth) return 0; | |
599 | |
600 // only type we support here at the moment | |
601 SparseTimeValueModel *stvm = qobject_cast<SparseTimeValueModel *>(model); | |
602 if (stvm->getScaleUnits() != "Hz") return 0; | |
603 | |
604 size_t blocks = frames / m_processingBlockSize; | |
605 | |
606 //!!! todo: see comment in mixClipModel | |
607 | |
608 size_t got = blocks * m_processingBlockSize; | |
609 | |
610 #ifdef DEBUG_AUDIO_GENERATOR | |
611 cout << "mixModel [synth]: frames " << frames | |
612 << ", blocks " << blocks << endl; | |
613 #endif | |
614 | |
615 float **bufferIndexes = new float *[m_targetChannelCount]; | |
616 | |
617 for (size_t i = 0; i < blocks; ++i) { | |
618 | |
619 size_t reqStart = startFrame + i * m_processingBlockSize; | |
620 | |
621 for (size_t c = 0; c < m_targetChannelCount; ++c) { | |
622 bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; | |
623 } | |
624 | |
625 SparseTimeValueModel::PointList points = | |
626 stvm->getPoints(reqStart, reqStart + m_processingBlockSize); | |
627 | |
628 // by default, repeat last frequency | |
629 float f0 = 0.f; | |
630 | |
631 // go straight to the last freq that is genuinely in this range | |
632 for (SparseTimeValueModel::PointList::const_iterator itr = points.end(); | |
633 itr != points.begin(); ) { | |
634 --itr; | |
635 if (itr->frame >= reqStart && | |
636 itr->frame < reqStart + m_processingBlockSize) { | |
637 f0 = itr->value; | |
638 break; | |
639 } | |
640 } | |
641 | |
642 cerr << "f0 = " << f0 << endl; | |
643 | |
644 synth->mix(bufferIndexes, | |
645 gain, | |
646 pan, | |
647 f0); | |
648 } | |
649 | |
650 delete[] bufferIndexes; | |
651 | |
652 return got; | |
653 } | |
654 |