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