Mercurial > hg > svapp
comparison audioio/AudioGenerator.cpp @ 350:aebee52e86b3
Merge from branch tony_integration
author | Chris Cannam |
---|---|
date | Wed, 14 May 2014 09:54:46 +0100 |
parents | 8d7f39df44ed |
children | 0876ea394902 |
comparison
equal
deleted
inserted
replaced
330:46b24009ce7a | 350:aebee52e86b3 |
---|---|
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::wantsQuieterClips(const Model *model) |
170 return; | 177 { |
171 } | 178 // basically, anything that usually has sustain (like notes) or |
172 | 179 // often has multiple sounds at once (like notes) wants to use a |
173 if (m_synthMap.find(model) == m_synthMap.end()) { | 180 // quieter level than simple click tracks |
174 SVDEBUG << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << endl; | 181 bool does = |
175 return; | 182 (qobject_cast<const NoteModel *>(model) || |
176 } | 183 qobject_cast<const FlexiNoteModel *>(model)); |
177 | 184 return does; |
178 RealTimePluginInstance *plugin = m_synthMap[model]; | 185 } |
179 if (plugin) { | 186 |
180 PluginXml(plugin).setParametersFromXml(configurationXml); | 187 bool |
181 } | 188 AudioGenerator::usesContinuousSynth(const Model *model) |
182 } | 189 { |
183 | 190 bool cont = |
184 void | 191 (qobject_cast<const SparseTimeValueModel *>(model)); |
185 AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) | 192 return cont; |
186 { | 193 } |
187 if (m_sampleDir != "") { | 194 |
188 plugin->configure("sampledir", m_sampleDir.toStdString()); | 195 ClipMixer * |
189 } | 196 AudioGenerator::makeClipMixerFor(const Model *model) |
190 } | 197 { |
191 | 198 QString clipId; |
192 RealTimePluginInstance * | |
193 AudioGenerator::loadPluginFor(const Model *model) | |
194 { | |
195 QString pluginId, configurationXml; | |
196 | 199 |
197 const Playable *playable = model; | 200 const Playable *playable = model; |
198 if (!playable || !playable->canPlay()) return 0; | 201 if (!playable || !playable->canPlay()) return 0; |
199 | 202 |
200 PlayParameters *parameters = | 203 PlayParameters *parameters = |
201 PlayParameterRepository::getInstance()->getPlayParameters(playable); | 204 PlayParameterRepository::getInstance()->getPlayParameters(playable); |
202 if (parameters) { | 205 if (parameters) { |
203 pluginId = parameters->getPlayPluginId(); | 206 clipId = parameters->getPlayClipId(); |
204 configurationXml = parameters->getPlayPluginConfiguration(); | 207 } |
205 } | 208 |
206 | 209 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): sample id = " << clipId << std::endl; |
207 if (pluginId == "") return 0; | 210 |
208 | 211 if (clipId == "") { |
209 RealTimePluginInstance *plugin = loadPlugin(pluginId, ""); | 212 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; | 213 return 0; |
245 } | 214 } |
246 | 215 |
247 setSampleDir(instance); | 216 ClipMixer *mixer = new ClipMixer(m_targetChannelCount, |
248 | 217 m_sourceSampleRate, |
249 for (unsigned int i = 0; i < instance->getParameterCount(); ++i) { | 218 m_processingBlockSize); |
250 instance->setParameterValue(i, instance->getParameterDefault(i)); | 219 |
251 } | 220 float clipF0 = Pitch::getFrequencyForPitch(60, 0, 440.0f); // required |
252 std::string defaultProgram = instance->getProgram(0, 0); | 221 |
253 if (defaultProgram != "") { | 222 QString clipPath = QString("%1/%2.wav").arg(m_sampleDir).arg(clipId); |
254 // cerr << "first selecting default program " << defaultProgram << endl; | 223 |
255 instance->selectProgram(defaultProgram); | 224 float level = wantsQuieterClips(model) ? 0.5 : 1.0; |
256 } | 225 if (!mixer->loadClipData(clipPath, clipF0, level)) { |
257 if (program != "") { | 226 delete mixer; |
258 // cerr << "now selecting desired program " << program << endl; | 227 return 0; |
259 instance->selectProgram(program.toStdString()); | 228 } |
260 } | 229 |
261 instance->setIdealChannelCount(m_targetChannelCount); // reset! | 230 std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): loaded clip " << clipId << std::endl; |
262 | 231 |
263 return instance; | 232 return mixer; |
233 } | |
234 | |
235 ContinuousSynth * | |
236 AudioGenerator::makeSynthFor(const Model *model) | |
237 { | |
238 const Playable *playable = model; | |
239 if (!playable || !playable->canPlay()) return 0; | |
240 | |
241 ContinuousSynth *synth = new ContinuousSynth(m_targetChannelCount, | |
242 m_sourceSampleRate, | |
243 m_processingBlockSize, | |
244 m_waveType); | |
245 | |
246 std::cerr << "AudioGenerator::makeSynthFor(" << model << "): created synth" << std::endl; | |
247 | |
248 return synth; | |
264 } | 249 } |
265 | 250 |
266 void | 251 void |
267 AudioGenerator::removeModel(Model *model) | 252 AudioGenerator::removeModel(Model *model) |
268 { | 253 { |
270 dynamic_cast<SparseOneDimensionalModel *>(model); | 255 dynamic_cast<SparseOneDimensionalModel *>(model); |
271 if (!sodm) return; // nothing to do | 256 if (!sodm) return; // nothing to do |
272 | 257 |
273 QMutexLocker locker(&m_mutex); | 258 QMutexLocker locker(&m_mutex); |
274 | 259 |
275 if (m_synthMap.find(sodm) == m_synthMap.end()) return; | 260 if (m_clipMixerMap.find(sodm) == m_clipMixerMap.end()) return; |
276 | 261 |
277 RealTimePluginInstance *instance = m_synthMap[sodm]; | 262 ClipMixer *mixer = m_clipMixerMap[sodm]; |
278 m_synthMap.erase(sodm); | 263 m_clipMixerMap.erase(sodm); |
279 delete instance; | 264 delete mixer; |
280 } | 265 } |
281 | 266 |
282 void | 267 void |
283 AudioGenerator::clearModels() | 268 AudioGenerator::clearModels() |
284 { | 269 { |
285 QMutexLocker locker(&m_mutex); | 270 QMutexLocker locker(&m_mutex); |
286 while (!m_synthMap.empty()) { | 271 |
287 RealTimePluginInstance *instance = m_synthMap.begin()->second; | 272 while (!m_clipMixerMap.empty()) { |
288 m_synthMap.erase(m_synthMap.begin()); | 273 ClipMixer *mixer = m_clipMixerMap.begin()->second; |
289 delete instance; | 274 m_clipMixerMap.erase(m_clipMixerMap.begin()); |
275 delete mixer; | |
290 } | 276 } |
291 } | 277 } |
292 | 278 |
293 void | 279 void |
294 AudioGenerator::reset() | 280 AudioGenerator::reset() |
295 { | 281 { |
296 QMutexLocker locker(&m_mutex); | 282 QMutexLocker locker(&m_mutex); |
297 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { | 283 |
284 for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { | |
298 if (i->second) { | 285 if (i->second) { |
299 i->second->silence(); | 286 i->second->reset(); |
300 i->second->discardEvents(); | |
301 } | 287 } |
302 } | 288 } |
303 | 289 |
304 m_noteOffs.clear(); | 290 m_noteOffs.clear(); |
305 } | 291 } |
312 // SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; | 298 // SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; |
313 | 299 |
314 QMutexLocker locker(&m_mutex); | 300 QMutexLocker locker(&m_mutex); |
315 m_targetChannelCount = targetChannelCount; | 301 m_targetChannelCount = targetChannelCount; |
316 | 302 |
317 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { | 303 for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { |
318 if (i->second) i->second->setIdealChannelCount(targetChannelCount); | 304 if (i->second) i->second->setChannelCount(targetChannelCount); |
319 } | 305 } |
320 } | 306 } |
321 | 307 |
322 size_t | 308 size_t |
323 AudioGenerator::getBlockSize() const | 309 AudioGenerator::getBlockSize() const |
324 { | 310 { |
325 return m_pluginBlockSize; | 311 return m_processingBlockSize; |
326 } | 312 } |
327 | 313 |
328 void | 314 void |
329 AudioGenerator::setSoloModelSet(std::set<Model *> s) | 315 AudioGenerator::setSoloModelSet(std::set<Model *> s) |
330 { | 316 { |
385 if (dtvm) { | 371 if (dtvm) { |
386 return mixDenseTimeValueModel(dtvm, startFrame, frameCount, | 372 return mixDenseTimeValueModel(dtvm, startFrame, frameCount, |
387 buffer, gain, pan, fadeIn, fadeOut); | 373 buffer, gain, pan, fadeIn, fadeOut); |
388 } | 374 } |
389 | 375 |
390 bool synthetic = | 376 if (usesClipMixer(model)) { |
391 (qobject_cast<SparseOneDimensionalModel *>(model) || | 377 return mixClipModel(model, startFrame, frameCount, |
392 qobject_cast<NoteModel *>(model)); | 378 buffer, gain, pan); |
393 | 379 } |
394 if (synthetic) { | 380 |
395 return mixSyntheticNoteModel(model, startFrame, frameCount, | 381 if (usesContinuousSynth(model)) { |
396 buffer, gain, pan, fadeIn, fadeOut); | 382 return mixContinuousSynthModel(model, startFrame, frameCount, |
397 } | 383 buffer, gain, pan); |
384 } | |
385 | |
386 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 | 387 |
399 return frameCount; | 388 return frameCount; |
400 } | 389 } |
401 | 390 |
402 size_t | 391 size_t |
493 | 482 |
494 return got; | 483 return got; |
495 } | 484 } |
496 | 485 |
497 size_t | 486 size_t |
498 AudioGenerator::mixSyntheticNoteModel(Model *model, | 487 AudioGenerator::mixClipModel(Model *model, |
499 size_t startFrame, size_t frames, | 488 size_t startFrame, size_t frames, |
500 float **buffer, float gain, float pan, | 489 float **buffer, float gain, float pan) |
501 size_t /* fadeIn */, | 490 { |
502 size_t /* fadeOut */) | 491 ClipMixer *clipMixer = m_clipMixerMap[model]; |
503 { | 492 if (!clipMixer) return 0; |
504 RealTimePluginInstance *plugin = m_synthMap[model]; | 493 |
505 if (!plugin) return 0; | 494 size_t blocks = frames / m_processingBlockSize; |
506 | |
507 size_t latency = plugin->getLatency(); | |
508 size_t blocks = frames / m_pluginBlockSize; | |
509 | 495 |
496 //!!! todo: the below -- it matters | |
497 | |
510 //!!! hang on -- the fact that the audio callback play source's | 498 //!!! 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 | 499 //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 | 500 //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 | 501 //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 | 502 //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 | 503 //always have a multiple of the plugin buffer size? I guess this |
516 //class has to be queryable for the plugin buffer size & the | 504 //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 | 505 //callback play source has to use that as a multiple for all the |
518 //calls to mixModel | 506 //calls to mixModel |
519 | 507 |
520 size_t got = blocks * m_pluginBlockSize; | 508 size_t got = blocks * m_processingBlockSize; |
521 | 509 |
522 #ifdef DEBUG_AUDIO_GENERATOR | 510 #ifdef DEBUG_AUDIO_GENERATOR |
523 cout << "mixModel [synthetic note]: frames " << frames | 511 cout << "mixModel [clip]: frames " << frames |
524 << ", blocks " << blocks << endl; | 512 << ", blocks " << blocks << endl; |
525 #endif | 513 #endif |
526 | 514 |
527 snd_seq_event_t onEv; | 515 ClipMixer::NoteStart on; |
528 onEv.type = SND_SEQ_EVENT_NOTEON; | 516 ClipMixer::NoteEnd off; |
529 onEv.data.note.channel = 0; | 517 |
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]; | 518 NoteOffSet ¬eOffs = m_noteOffs[model]; |
537 | 519 |
520 float **bufferIndexes = new float *[m_targetChannelCount]; | |
521 | |
538 for (size_t i = 0; i < blocks; ++i) { | 522 for (size_t i = 0; i < blocks; ++i) { |
539 | 523 |
540 size_t reqStart = startFrame + i * m_pluginBlockSize; | 524 size_t reqStart = startFrame + i * m_processingBlockSize; |
541 | 525 |
542 NoteList notes = getNotes(model, | 526 NoteList notes; |
543 reqStart + latency, | 527 NoteExportable *exportable = dynamic_cast<NoteExportable *>(model); |
544 reqStart + latency + m_pluginBlockSize); | 528 if (exportable) { |
545 | 529 notes = exportable->getNotes(reqStart, |
546 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime | 530 reqStart + m_processingBlockSize); |
547 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); | 531 } |
532 | |
533 std::vector<ClipMixer::NoteStart> starts; | |
534 std::vector<ClipMixer::NoteEnd> ends; | |
548 | 535 |
549 for (NoteList::const_iterator ni = notes.begin(); | 536 for (NoteList::const_iterator ni = notes.begin(); |
550 ni != notes.end(); ++ni) { | 537 ni != notes.end(); ++ni) { |
551 | 538 |
552 size_t noteFrame = ni->start; | 539 size_t noteFrame = ni->start; |
553 | 540 |
554 if (noteFrame >= latency) noteFrame -= latency; | |
555 | |
556 if (noteFrame < reqStart || | 541 if (noteFrame < reqStart || |
557 noteFrame >= reqStart + m_pluginBlockSize) continue; | 542 noteFrame >= reqStart + m_processingBlockSize) continue; |
558 | 543 |
559 while (noteOffs.begin() != noteOffs.end() && | 544 while (noteOffs.begin() != noteOffs.end() && |
560 noteOffs.begin()->frame <= noteFrame) { | 545 noteOffs.begin()->frame <= noteFrame) { |
561 | 546 |
562 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 547 size_t eventFrame = noteOffs.begin()->frame; |
563 (noteOffs.begin()->frame, m_sourceSampleRate); | 548 if (eventFrame < reqStart) eventFrame = reqStart; |
564 | 549 |
565 offEv.data.note.note = noteOffs.begin()->pitch; | 550 off.frameOffset = eventFrame - reqStart; |
566 | 551 off.frequency = noteOffs.begin()->frequency; |
567 #ifdef DEBUG_AUDIO_GENERATOR | 552 |
568 cerr << "mixModel [synthetic]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; | 553 #ifdef DEBUG_AUDIO_GENERATOR |
569 #endif | 554 cerr << "mixModel [clip]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; |
570 | 555 #endif |
571 plugin->sendEvent(eventTime, &offEv); | 556 |
557 ends.push_back(off); | |
572 noteOffs.erase(noteOffs.begin()); | 558 noteOffs.erase(noteOffs.begin()); |
573 } | 559 } |
574 | 560 |
575 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 561 on.frameOffset = noteFrame - reqStart; |
576 (noteFrame, m_sourceSampleRate); | 562 on.frequency = ni->getFrequency(); |
563 on.level = float(ni->velocity) / 127.0; | |
564 on.pan = pan; | |
565 | |
566 #ifdef DEBUG_AUDIO_GENERATOR | |
567 cout << "mixModel [clip]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << ", level " << on.level << endl; | |
568 #endif | |
577 | 569 |
578 if (ni->isMidiPitchQuantized) { | 570 starts.push_back(on); |
579 onEv.data.note.note = ni->midiPitch; | 571 noteOffs.insert |
580 } else { | 572 (NoteOff(on.frequency, noteFrame + ni->duration)); |
581 #ifdef DEBUG_AUDIO_GENERATOR | 573 } |
582 cerr << "mixModel [synthetic]: non-pitch-quantized notes are not supported [yet], quantizing" << endl; | 574 |
583 #endif | 575 while (noteOffs.begin() != noteOffs.end() && |
584 onEv.data.note.note = Pitch::getPitchForFrequency(ni->frequency); | 576 noteOffs.begin()->frame <= reqStart + m_processingBlockSize) { |
577 | |
578 size_t eventFrame = noteOffs.begin()->frame; | |
579 if (eventFrame < reqStart) eventFrame = reqStart; | |
580 | |
581 off.frameOffset = eventFrame - reqStart; | |
582 off.frequency = noteOffs.begin()->frequency; | |
583 | |
584 #ifdef DEBUG_AUDIO_GENERATOR | |
585 cerr << "mixModel [clip]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; | |
586 #endif | |
587 | |
588 ends.push_back(off); | |
589 noteOffs.erase(noteOffs.begin()); | |
590 } | |
591 | |
592 for (size_t c = 0; c < m_targetChannelCount; ++c) { | |
593 bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; | |
594 } | |
595 | |
596 clipMixer->mix(bufferIndexes, gain, starts, ends); | |
597 } | |
598 | |
599 delete[] bufferIndexes; | |
600 | |
601 return got; | |
602 } | |
603 | |
604 size_t | |
605 AudioGenerator::mixContinuousSynthModel(Model *model, | |
606 size_t startFrame, | |
607 size_t frames, | |
608 float **buffer, | |
609 float gain, | |
610 float pan) | |
611 { | |
612 ContinuousSynth *synth = m_continuousSynthMap[model]; | |
613 if (!synth) return 0; | |
614 | |
615 // only type we support here at the moment | |
616 SparseTimeValueModel *stvm = qobject_cast<SparseTimeValueModel *>(model); | |
617 if (stvm->getScaleUnits() != "Hz") return 0; | |
618 | |
619 size_t blocks = frames / m_processingBlockSize; | |
620 | |
621 //!!! todo: see comment in mixClipModel | |
622 | |
623 size_t got = blocks * m_processingBlockSize; | |
624 | |
625 #ifdef DEBUG_AUDIO_GENERATOR | |
626 cout << "mixModel [synth]: frames " << frames | |
627 << ", blocks " << blocks << endl; | |
628 #endif | |
629 | |
630 float **bufferIndexes = new float *[m_targetChannelCount]; | |
631 | |
632 for (size_t i = 0; i < blocks; ++i) { | |
633 | |
634 size_t reqStart = startFrame + i * m_processingBlockSize; | |
635 | |
636 for (size_t c = 0; c < m_targetChannelCount; ++c) { | |
637 bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; | |
638 } | |
639 | |
640 SparseTimeValueModel::PointList points = | |
641 stvm->getPoints(reqStart, reqStart + m_processingBlockSize); | |
642 | |
643 // by default, repeat last frequency | |
644 float f0 = 0.f; | |
645 | |
646 // go straight to the last freq that is genuinely in this range | |
647 for (SparseTimeValueModel::PointList::const_iterator itr = points.end(); | |
648 itr != points.begin(); ) { | |
649 --itr; | |
650 if (itr->frame >= reqStart && | |
651 itr->frame < reqStart + m_processingBlockSize) { | |
652 f0 = itr->value; | |
653 break; | |
585 } | 654 } |
586 | 655 } |
587 onEv.data.note.velocity = ni->velocity; | 656 |
588 | 657 // if we found no such frequency and the next point is further |
589 plugin->sendEvent(eventTime, &onEv); | 658 // away than twice the model resolution, go silent (same |
590 | 659 // criterion TimeValueLayer uses for ending a discrete curve |
591 #ifdef DEBUG_AUDIO_GENERATOR | 660 // segment) |
592 cout << "mixModel [synthetic]: note at frame " << noteFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << endl; | 661 if (f0 == 0.f) { |
593 #endif | 662 SparseTimeValueModel::PointList nextPoints = |
594 | 663 stvm->getNextPoints(reqStart + m_processingBlockSize); |
595 noteOffs.insert | 664 if (nextPoints.empty() || |
596 (NoteOff(onEv.data.note.note, noteFrame + ni->duration)); | 665 nextPoints.begin()->frame > reqStart + 2 * stvm->getResolution()) { |
597 } | 666 f0 = -1.f; |
598 | 667 } |
599 while (noteOffs.begin() != noteOffs.end() && | 668 } |
600 noteOffs.begin()->frame <= | 669 |
601 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { | 670 // cerr << "f0 = " << f0 << endl; |
602 | 671 |
603 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime | 672 synth->mix(bufferIndexes, |
604 (noteOffs.begin()->frame, m_sourceSampleRate); | 673 gain, |
605 | 674 pan, |
606 offEv.data.note.note = noteOffs.begin()->pitch; | 675 f0); |
607 | 676 } |
608 #ifdef DEBUG_AUDIO_GENERATOR | 677 |
609 cerr << "mixModel [synthetic]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; | 678 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 | 679 |
642 return got; | 680 return got; |
643 } | 681 } |
644 | 682 |
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 |