annotate audioio/AudioGenerator.cpp @ 201:de783e8ee5f0

* Hoist alignment model set/query up to Model, so any models can be aligned * Add Model::aboutToDelete and aboutToBeDeleted for management of models that are contained by or referred to by other models instead of only the document
author Chris Cannam
date Wed, 24 Oct 2007 15:21:38 +0000
parents f3191ab6d564
children
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 Sonic Visualiser
Chris@0 5 An audio file viewer and annotation editor.
Chris@0 6 Centre for Digital Music, Queen Mary, University of London.
Chris@0 7 This file copyright 2006 Chris Cannam.
Chris@0 8
Chris@0 9 This program is free software; you can redistribute it and/or
Chris@0 10 modify it under the terms of the GNU General Public License as
Chris@0 11 published by the Free Software Foundation; either version 2 of the
Chris@0 12 License, or (at your option) any later version. See the file
Chris@0 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include "AudioGenerator.h"
Chris@0 17
Chris@0 18 #include "base/TempDirectory.h"
Chris@0 19 #include "base/PlayParameters.h"
Chris@0 20 #include "base/PlayParameterRepository.h"
Chris@0 21 #include "base/Pitch.h"
Chris@0 22 #include "base/Exceptions.h"
Chris@0 23
Chris@1 24 #include "data/model/NoteModel.h"
Chris@1 25 #include "data/model/DenseTimeValueModel.h"
Chris@1 26 #include "data/model/SparseOneDimensionalModel.h"
Chris@0 27
Chris@0 28 #include "plugin/RealTimePluginFactory.h"
Chris@0 29 #include "plugin/RealTimePluginInstance.h"
Chris@0 30 #include "plugin/PluginIdentifier.h"
Chris@0 31 #include "plugin/PluginXml.h"
Chris@0 32 #include "plugin/api/alsa/seq_event.h"
Chris@0 33
Chris@0 34 #include <iostream>
Chris@0 35 #include <math.h>
Chris@0 36
Chris@0 37 #include <QDir>
Chris@0 38 #include <QFile>
Chris@0 39
Chris@0 40 const size_t
Chris@0 41 AudioGenerator::m_pluginBlockSize = 2048;
Chris@0 42
Chris@0 43 QString
Chris@0 44 AudioGenerator::m_sampleDir = "";
Chris@0 45
Chris@0 46 //#define DEBUG_AUDIO_GENERATOR 1
Chris@0 47
Chris@0 48 AudioGenerator::AudioGenerator() :
Chris@0 49 m_sourceSampleRate(0),
Chris@180 50 m_targetChannelCount(1),
Chris@180 51 m_soloing(false)
Chris@0 52 {
Chris@0 53 connect(PlayParameterRepository::getInstance(),
Chris@0 54 SIGNAL(playPluginIdChanged(const Model *, QString)),
Chris@0 55 this,
Chris@0 56 SLOT(playPluginIdChanged(const Model *, QString)));
Chris@0 57
Chris@0 58 connect(PlayParameterRepository::getInstance(),
Chris@0 59 SIGNAL(playPluginConfigurationChanged(const Model *, QString)),
Chris@0 60 this,
Chris@0 61 SLOT(playPluginConfigurationChanged(const Model *, QString)));
Chris@0 62 }
Chris@0 63
Chris@0 64 AudioGenerator::~AudioGenerator()
Chris@0 65 {
Chris@0 66 }
Chris@0 67
Chris@0 68 bool
Chris@0 69 AudioGenerator::canPlay(const Model *model)
Chris@0 70 {
Chris@0 71 if (dynamic_cast<const DenseTimeValueModel *>(model) ||
Chris@0 72 dynamic_cast<const SparseOneDimensionalModel *>(model) ||
Chris@0 73 dynamic_cast<const NoteModel *>(model)) {
Chris@0 74 return true;
Chris@0 75 } else {
Chris@0 76 return false;
Chris@0 77 }
Chris@0 78 }
Chris@0 79
Chris@0 80 bool
Chris@0 81 AudioGenerator::addModel(Model *model)
Chris@0 82 {
Chris@0 83 if (m_sourceSampleRate == 0) {
Chris@0 84
Chris@0 85 m_sourceSampleRate = model->getSampleRate();
Chris@0 86
Chris@0 87 } else {
Chris@0 88
Chris@0 89 DenseTimeValueModel *dtvm =
Chris@0 90 dynamic_cast<DenseTimeValueModel *>(model);
Chris@0 91
Chris@0 92 if (dtvm) {
Chris@0 93 m_sourceSampleRate = model->getSampleRate();
Chris@0 94 return true;
Chris@0 95 }
Chris@0 96 }
Chris@0 97
Chris@0 98 RealTimePluginInstance *plugin = loadPluginFor(model);
Chris@0 99 if (plugin) {
Chris@0 100 QMutexLocker locker(&m_mutex);
Chris@0 101 m_synthMap[model] = plugin;
Chris@0 102 return true;
Chris@0 103 }
Chris@0 104
Chris@0 105 return false;
Chris@0 106 }
Chris@0 107
Chris@0 108 void
Chris@0 109 AudioGenerator::playPluginIdChanged(const Model *model, QString)
Chris@0 110 {
Chris@0 111 if (m_synthMap.find(model) == m_synthMap.end()) return;
Chris@0 112
Chris@0 113 RealTimePluginInstance *plugin = loadPluginFor(model);
Chris@0 114 if (plugin) {
Chris@0 115 QMutexLocker locker(&m_mutex);
Chris@0 116 delete m_synthMap[model];
Chris@0 117 m_synthMap[model] = plugin;
Chris@0 118 }
Chris@0 119 }
Chris@0 120
Chris@0 121 void
Chris@0 122 AudioGenerator::playPluginConfigurationChanged(const Model *model,
Chris@0 123 QString configurationXml)
Chris@0 124 {
Chris@0 125 // std::cerr << "AudioGenerator::playPluginConfigurationChanged" << std::endl;
Chris@0 126
Chris@0 127 if (m_synthMap.find(model) == m_synthMap.end()) {
Chris@0 128 std::cerr << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << std::endl;
Chris@0 129 return;
Chris@0 130 }
Chris@0 131
Chris@0 132 RealTimePluginInstance *plugin = m_synthMap[model];
Chris@0 133 if (plugin) {
Chris@0 134 PluginXml(plugin).setParametersFromXml(configurationXml);
Chris@0 135 }
Chris@0 136 }
Chris@0 137
Chris@0 138 QString
Chris@0 139 AudioGenerator::getDefaultPlayPluginId(const Model *model)
Chris@0 140 {
Chris@0 141 const SparseOneDimensionalModel *sodm =
Chris@0 142 dynamic_cast<const SparseOneDimensionalModel *>(model);
Chris@0 143 if (sodm) {
Chris@0 144 return QString("dssi:%1:sample_player").
Chris@0 145 arg(PluginIdentifier::BUILTIN_PLUGIN_SONAME);
Chris@0 146 }
Chris@0 147
Chris@0 148 const NoteModel *nm = dynamic_cast<const NoteModel *>(model);
Chris@0 149 if (nm) {
Chris@0 150 return QString("dssi:%1:sample_player").
Chris@0 151 arg(PluginIdentifier::BUILTIN_PLUGIN_SONAME);
Chris@0 152 }
Chris@0 153
Chris@0 154 return "";
Chris@0 155 }
Chris@0 156
Chris@0 157 QString
Chris@0 158 AudioGenerator::getDefaultPlayPluginConfiguration(const Model *model)
Chris@0 159 {
Chris@0 160 QString program = "";
Chris@0 161
Chris@0 162 const SparseOneDimensionalModel *sodm =
Chris@0 163 dynamic_cast<const SparseOneDimensionalModel *>(model);
Chris@0 164 if (sodm) {
Chris@0 165 program = "tap";
Chris@0 166 }
Chris@0 167
Chris@0 168 const NoteModel *nm = dynamic_cast<const NoteModel *>(model);
Chris@0 169 if (nm) {
Chris@0 170 program = "piano";
Chris@0 171 }
Chris@0 172
Chris@0 173 if (program == "") return "";
Chris@0 174
Chris@0 175 return
Chris@0 176 QString("<plugin configuration=\"%1\" program=\"%2\"/>")
Chris@0 177 .arg(XmlExportable::encodeEntities
Chris@0 178 (QString("sampledir=%1")
Chris@0 179 .arg(PluginXml::encodeConfigurationChars(getSampleDir()))))
Chris@0 180 .arg(XmlExportable::encodeEntities(program));
Chris@0 181 }
Chris@0 182
Chris@0 183 QString
Chris@0 184 AudioGenerator::getSampleDir()
Chris@0 185 {
Chris@0 186 if (m_sampleDir != "") return m_sampleDir;
Chris@0 187
Chris@0 188 try {
Chris@0 189 m_sampleDir = TempDirectory::getInstance()->getSubDirectoryPath("samples");
Chris@0 190 } catch (DirectoryCreationFailed f) {
Chris@0 191 std::cerr << "WARNING: AudioGenerator::getSampleDir: Failed to create "
Chris@0 192 << "temporary sample directory" << std::endl;
Chris@0 193 m_sampleDir = "";
Chris@0 194 return "";
Chris@0 195 }
Chris@0 196
Chris@0 197 QDir sampleResourceDir(":/samples", "*.wav");
Chris@0 198
Chris@0 199 for (unsigned int i = 0; i < sampleResourceDir.count(); ++i) {
Chris@0 200
Chris@0 201 QString fileName(sampleResourceDir[i]);
Chris@0 202 QFile file(sampleResourceDir.filePath(fileName));
Chris@0 203
Chris@0 204 if (!file.copy(QDir(m_sampleDir).filePath(fileName))) {
Chris@0 205 std::cerr << "WARNING: AudioGenerator::getSampleDir: "
Chris@0 206 << "Unable to copy " << fileName.toStdString()
Chris@0 207 << " into temporary directory \""
Chris@0 208 << m_sampleDir.toStdString() << "\"" << std::endl;
Chris@0 209 }
Chris@0 210 }
Chris@0 211
Chris@0 212 return m_sampleDir;
Chris@0 213 }
Chris@0 214
Chris@0 215 void
Chris@0 216 AudioGenerator::setSampleDir(RealTimePluginInstance *plugin)
Chris@0 217 {
Chris@0 218 plugin->configure("sampledir", getSampleDir().toStdString());
Chris@0 219 }
Chris@0 220
Chris@0 221 RealTimePluginInstance *
Chris@0 222 AudioGenerator::loadPluginFor(const Model *model)
Chris@0 223 {
Chris@0 224 QString pluginId, configurationXml;
Chris@0 225
Chris@0 226 PlayParameters *parameters =
Chris@0 227 PlayParameterRepository::getInstance()->getPlayParameters(model);
Chris@0 228 if (parameters) {
Chris@0 229 pluginId = parameters->getPlayPluginId();
Chris@0 230 configurationXml = parameters->getPlayPluginConfiguration();
Chris@0 231 }
Chris@0 232
Chris@0 233 if (pluginId == "") {
Chris@0 234 pluginId = getDefaultPlayPluginId(model);
Chris@0 235 configurationXml = getDefaultPlayPluginConfiguration(model);
Chris@0 236 }
Chris@0 237
Chris@0 238 if (pluginId == "") return 0;
Chris@0 239
Chris@0 240 RealTimePluginInstance *plugin = loadPlugin(pluginId, "");
Chris@0 241 if (!plugin) return 0;
Chris@0 242
Chris@0 243 if (configurationXml != "") {
Chris@0 244 PluginXml(plugin).setParametersFromXml(configurationXml);
Chris@0 245 }
Chris@0 246
Chris@0 247 if (parameters) {
Chris@0 248 parameters->setPlayPluginId(pluginId);
Chris@0 249 parameters->setPlayPluginConfiguration(configurationXml);
Chris@0 250 }
Chris@0 251
Chris@0 252 return plugin;
Chris@0 253 }
Chris@0 254
Chris@0 255 RealTimePluginInstance *
Chris@0 256 AudioGenerator::loadPlugin(QString pluginId, QString program)
Chris@0 257 {
Chris@0 258 RealTimePluginFactory *factory =
Chris@0 259 RealTimePluginFactory::instanceFor(pluginId);
Chris@0 260
Chris@0 261 if (!factory) {
Chris@0 262 std::cerr << "Failed to get plugin factory" << std::endl;
Chris@0 263 return false;
Chris@0 264 }
Chris@0 265
Chris@0 266 RealTimePluginInstance *instance =
Chris@0 267 factory->instantiatePlugin
Chris@0 268 (pluginId, 0, 0, m_sourceSampleRate, m_pluginBlockSize, m_targetChannelCount);
Chris@0 269
Chris@0 270 if (!instance) {
Chris@0 271 std::cerr << "Failed to instantiate plugin " << pluginId.toStdString() << std::endl;
Chris@0 272 return 0;
Chris@0 273 }
Chris@0 274
Chris@0 275 setSampleDir(instance);
Chris@0 276
Chris@0 277 for (unsigned int i = 0; i < instance->getParameterCount(); ++i) {
Chris@0 278 instance->setParameterValue(i, instance->getParameterDefault(i));
Chris@0 279 }
Chris@0 280 std::string defaultProgram = instance->getProgram(0, 0);
Chris@0 281 if (defaultProgram != "") {
Chris@0 282 // std::cerr << "first selecting default program " << defaultProgram << std::endl;
Chris@0 283 instance->selectProgram(defaultProgram);
Chris@0 284 }
Chris@0 285 if (program != "") {
Chris@0 286 // std::cerr << "now selecting desired program " << program.toStdString() << std::endl;
Chris@0 287 instance->selectProgram(program.toStdString());
Chris@0 288 }
Chris@0 289 instance->setIdealChannelCount(m_targetChannelCount); // reset!
Chris@0 290
Chris@0 291 return instance;
Chris@0 292 }
Chris@0 293
Chris@0 294 void
Chris@0 295 AudioGenerator::removeModel(Model *model)
Chris@0 296 {
Chris@0 297 SparseOneDimensionalModel *sodm =
Chris@0 298 dynamic_cast<SparseOneDimensionalModel *>(model);
Chris@0 299 if (!sodm) return; // nothing to do
Chris@0 300
Chris@0 301 QMutexLocker locker(&m_mutex);
Chris@0 302
Chris@0 303 if (m_synthMap.find(sodm) == m_synthMap.end()) return;
Chris@0 304
Chris@0 305 RealTimePluginInstance *instance = m_synthMap[sodm];
Chris@0 306 m_synthMap.erase(sodm);
Chris@0 307 delete instance;
Chris@0 308 }
Chris@0 309
Chris@0 310 void
Chris@0 311 AudioGenerator::clearModels()
Chris@0 312 {
Chris@0 313 QMutexLocker locker(&m_mutex);
Chris@0 314 while (!m_synthMap.empty()) {
Chris@0 315 RealTimePluginInstance *instance = m_synthMap.begin()->second;
Chris@0 316 m_synthMap.erase(m_synthMap.begin());
Chris@0 317 delete instance;
Chris@0 318 }
Chris@0 319 }
Chris@0 320
Chris@0 321 void
Chris@0 322 AudioGenerator::reset()
Chris@0 323 {
Chris@0 324 QMutexLocker locker(&m_mutex);
Chris@0 325 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) {
Chris@0 326 if (i->second) {
Chris@0 327 i->second->silence();
Chris@0 328 i->second->discardEvents();
Chris@0 329 }
Chris@0 330 }
Chris@0 331
Chris@0 332 m_noteOffs.clear();
Chris@0 333 }
Chris@0 334
Chris@0 335 void
Chris@0 336 AudioGenerator::setTargetChannelCount(size_t targetChannelCount)
Chris@0 337 {
Chris@0 338 if (m_targetChannelCount == targetChannelCount) return;
Chris@0 339
Chris@0 340 // std::cerr << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << std::endl;
Chris@0 341
Chris@0 342 QMutexLocker locker(&m_mutex);
Chris@0 343 m_targetChannelCount = targetChannelCount;
Chris@0 344
Chris@0 345 for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) {
Chris@0 346 if (i->second) i->second->setIdealChannelCount(targetChannelCount);
Chris@0 347 }
Chris@0 348 }
Chris@0 349
Chris@0 350 size_t
Chris@0 351 AudioGenerator::getBlockSize() const
Chris@0 352 {
Chris@0 353 return m_pluginBlockSize;
Chris@0 354 }
Chris@0 355
Chris@180 356 void
Chris@180 357 AudioGenerator::setSoloModelSet(std::set<Model *> s)
Chris@180 358 {
Chris@180 359 QMutexLocker locker(&m_mutex);
Chris@180 360
Chris@180 361 m_soloModelSet = s;
Chris@180 362 m_soloing = true;
Chris@180 363 }
Chris@180 364
Chris@180 365 void
Chris@180 366 AudioGenerator::clearSoloModelSet()
Chris@180 367 {
Chris@180 368 QMutexLocker locker(&m_mutex);
Chris@180 369
Chris@180 370 m_soloModelSet.clear();
Chris@180 371 m_soloing = false;
Chris@180 372 }
Chris@180 373
Chris@0 374 size_t
Chris@0 375 AudioGenerator::mixModel(Model *model, size_t startFrame, size_t frameCount,
Chris@0 376 float **buffer, size_t fadeIn, size_t fadeOut)
Chris@0 377 {
Chris@0 378 if (m_sourceSampleRate == 0) {
Chris@0 379 std::cerr << "WARNING: AudioGenerator::mixModel: No base source sample rate available" << std::endl;
Chris@0 380 return frameCount;
Chris@0 381 }
Chris@0 382
Chris@0 383 QMutexLocker locker(&m_mutex);
Chris@0 384
Chris@0 385 PlayParameters *parameters =
Chris@0 386 PlayParameterRepository::getInstance()->getPlayParameters(model);
Chris@0 387 if (!parameters) return frameCount;
Chris@0 388
Chris@0 389 bool playing = !parameters->isPlayMuted();
Chris@118 390 if (!playing) {
Chris@118 391 #ifdef DEBUG_AUDIO_GENERATOR
Chris@118 392 std::cout << "AudioGenerator::mixModel(" << model << "): muted" << std::endl;
Chris@118 393 #endif
Chris@118 394 return frameCount;
Chris@118 395 }
Chris@0 396
Chris@180 397 if (m_soloing) {
Chris@180 398 if (m_soloModelSet.find(model) == m_soloModelSet.end()) {
Chris@180 399 #ifdef DEBUG_AUDIO_GENERATOR
Chris@180 400 std::cout << "AudioGenerator::mixModel(" << model << "): not one of the solo'd models" << std::endl;
Chris@180 401 #endif
Chris@180 402 return frameCount;
Chris@180 403 }
Chris@180 404 }
Chris@180 405
Chris@0 406 float gain = parameters->getPlayGain();
Chris@0 407 float pan = parameters->getPlayPan();
Chris@0 408
Chris@0 409 DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
Chris@0 410 if (dtvm) {
Chris@0 411 return mixDenseTimeValueModel(dtvm, startFrame, frameCount,
Chris@0 412 buffer, gain, pan, fadeIn, fadeOut);
Chris@0 413 }
Chris@0 414
Chris@0 415 SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
Chris@0 416 (model);
Chris@0 417 if (sodm) {
Chris@0 418 return mixSparseOneDimensionalModel(sodm, startFrame, frameCount,
Chris@0 419 buffer, gain, pan, fadeIn, fadeOut);
Chris@0 420 }
Chris@0 421
Chris@0 422 NoteModel *nm = dynamic_cast<NoteModel *>(model);
Chris@0 423 if (nm) {
Chris@0 424 return mixNoteModel(nm, startFrame, frameCount,
Chris@0 425 buffer, gain, pan, fadeIn, fadeOut);
Chris@0 426 }
Chris@0 427
Chris@0 428 return frameCount;
Chris@0 429 }
Chris@0 430
Chris@0 431 size_t
Chris@0 432 AudioGenerator::mixDenseTimeValueModel(DenseTimeValueModel *dtvm,
Chris@0 433 size_t startFrame, size_t frames,
Chris@0 434 float **buffer, float gain, float pan,
Chris@0 435 size_t fadeIn, size_t fadeOut)
Chris@0 436 {
Chris@0 437 static float *channelBuffer = 0;
Chris@0 438 static size_t channelBufSiz = 0;
Chris@0 439
Chris@0 440 size_t totalFrames = frames + fadeIn/2 + fadeOut/2;
Chris@0 441
Chris@0 442 if (channelBufSiz < totalFrames) {
Chris@0 443 delete[] channelBuffer;
Chris@0 444 channelBuffer = new float[totalFrames];
Chris@0 445 channelBufSiz = totalFrames;
Chris@0 446 }
Chris@0 447
Chris@0 448 size_t got = 0;
Chris@0 449 size_t prevChannel = 999;
Chris@0 450
Chris@0 451 for (size_t c = 0; c < m_targetChannelCount; ++c) {
Chris@0 452
Chris@0 453 size_t sourceChannel = (c % dtvm->getChannelCount());
Chris@0 454
Chris@0 455 // std::cerr << "mixing channel " << c << " from source channel " << sourceChannel << std::endl;
Chris@0 456
Chris@0 457 float channelGain = gain;
Chris@0 458 if (pan != 0.0) {
Chris@0 459 if (c == 0) {
Chris@0 460 if (pan > 0.0) channelGain *= 1.0 - pan;
Chris@0 461 } else {
Chris@0 462 if (pan < 0.0) channelGain *= pan + 1.0;
Chris@0 463 }
Chris@0 464 }
Chris@0 465
Chris@0 466 if (prevChannel != sourceChannel) {
Chris@0 467 if (startFrame >= fadeIn/2) {
Chris@184 468 got = dtvm->getData
Chris@0 469 (sourceChannel,
Chris@184 470 startFrame - fadeIn/2,
Chris@184 471 frames + fadeOut/2 + fadeIn/2,
Chris@0 472 channelBuffer);
Chris@0 473 } else {
Chris@0 474 size_t missing = fadeIn/2 - startFrame;
Chris@184 475 got = dtvm->getData
Chris@0 476 (sourceChannel,
Chris@184 477 startFrame,
Chris@184 478 frames + fadeOut/2,
Chris@0 479 channelBuffer + missing);
Chris@0 480 }
Chris@0 481 }
Chris@0 482 prevChannel = sourceChannel;
Chris@0 483
Chris@0 484 for (size_t i = 0; i < fadeIn/2; ++i) {
Chris@0 485 float *back = buffer[c];
Chris@0 486 back -= fadeIn/2;
Chris@0 487 back[i] += (channelGain * channelBuffer[i] * i) / fadeIn;
Chris@0 488 }
Chris@0 489
Chris@0 490 for (size_t i = 0; i < frames + fadeOut/2; ++i) {
Chris@0 491 float mult = channelGain;
Chris@0 492 if (i < fadeIn/2) {
Chris@0 493 mult = (mult * i) / fadeIn;
Chris@0 494 }
Chris@0 495 if (i > frames - fadeOut/2) {
Chris@0 496 mult = (mult * ((frames + fadeOut/2) - i)) / fadeOut;
Chris@0 497 }
Chris@0 498 buffer[c][i] += mult * channelBuffer[i];
Chris@0 499 }
Chris@0 500 }
Chris@0 501
Chris@0 502 return got;
Chris@0 503 }
Chris@0 504
Chris@0 505 size_t
Chris@0 506 AudioGenerator::mixSparseOneDimensionalModel(SparseOneDimensionalModel *sodm,
Chris@0 507 size_t startFrame, size_t frames,
Chris@0 508 float **buffer, float gain, float pan,
Chris@0 509 size_t /* fadeIn */,
Chris@0 510 size_t /* fadeOut */)
Chris@0 511 {
Chris@0 512 RealTimePluginInstance *plugin = m_synthMap[sodm];
Chris@0 513 if (!plugin) return 0;
Chris@0 514
Chris@0 515 size_t latency = plugin->getLatency();
Chris@0 516 size_t blocks = frames / m_pluginBlockSize;
Chris@0 517
Chris@0 518 //!!! hang on -- the fact that the audio callback play source's
Chris@0 519 //buffer is a multiple of the plugin's buffer size doesn't mean
Chris@0 520 //that we always get called for a multiple of it here (because it
Chris@0 521 //also depends on the JACK block size). how should we ensure that
Chris@0 522 //all models write the same amount in to the mix, and that we
Chris@0 523 //always have a multiple of the plugin buffer size? I guess this
Chris@0 524 //class has to be queryable for the plugin buffer size & the
Chris@0 525 //callback play source has to use that as a multiple for all the
Chris@0 526 //calls to mixModel
Chris@0 527
Chris@0 528 size_t got = blocks * m_pluginBlockSize;
Chris@0 529
Chris@0 530 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 531 std::cout << "mixModel [sparse]: frames " << frames
Chris@0 532 << ", blocks " << blocks << std::endl;
Chris@0 533 #endif
Chris@0 534
Chris@0 535 snd_seq_event_t onEv;
Chris@0 536 onEv.type = SND_SEQ_EVENT_NOTEON;
Chris@0 537 onEv.data.note.channel = 0;
Chris@0 538 onEv.data.note.note = 64;
Chris@185 539 onEv.data.note.velocity = 100;
Chris@0 540
Chris@0 541 snd_seq_event_t offEv;
Chris@0 542 offEv.type = SND_SEQ_EVENT_NOTEOFF;
Chris@0 543 offEv.data.note.channel = 0;
Chris@0 544 offEv.data.note.velocity = 0;
Chris@0 545
Chris@0 546 NoteOffSet &noteOffs = m_noteOffs[sodm];
Chris@0 547
Chris@0 548 for (size_t i = 0; i < blocks; ++i) {
Chris@0 549
Chris@0 550 size_t reqStart = startFrame + i * m_pluginBlockSize;
Chris@0 551
Chris@0 552 SparseOneDimensionalModel::PointList points =
Chris@0 553 sodm->getPoints(reqStart + latency,
Chris@0 554 reqStart + latency + m_pluginBlockSize);
Chris@0 555
Chris@0 556 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime
Chris@0 557 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate);
Chris@0 558
Chris@0 559 for (SparseOneDimensionalModel::PointList::iterator pli =
Chris@0 560 points.begin(); pli != points.end(); ++pli) {
Chris@0 561
Chris@0 562 size_t pliFrame = pli->frame;
Chris@0 563
Chris@0 564 if (pliFrame >= latency) pliFrame -= latency;
Chris@0 565
Chris@0 566 if (pliFrame < reqStart ||
Chris@0 567 pliFrame >= reqStart + m_pluginBlockSize) continue;
Chris@0 568
Chris@0 569 while (noteOffs.begin() != noteOffs.end() &&
Chris@0 570 noteOffs.begin()->frame <= pliFrame) {
Chris@0 571
Chris@0 572 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
Chris@0 573 (noteOffs.begin()->frame, m_sourceSampleRate);
Chris@0 574
Chris@0 575 offEv.data.note.note = noteOffs.begin()->pitch;
Chris@0 576
Chris@0 577 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 578 std::cerr << "mixModel [sparse]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl;
Chris@0 579 #endif
Chris@0 580
Chris@0 581 plugin->sendEvent(eventTime, &offEv);
Chris@0 582 noteOffs.erase(noteOffs.begin());
Chris@0 583 }
Chris@0 584
Chris@0 585 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
Chris@0 586 (pliFrame, m_sourceSampleRate);
Chris@0 587
Chris@0 588 plugin->sendEvent(eventTime, &onEv);
Chris@0 589
Chris@0 590 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 591 std::cout << "mixModel [sparse]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl;
Chris@0 592 #endif
Chris@0 593
Chris@0 594 size_t duration = 7000; // frames [for now]
Chris@0 595 NoteOff noff;
Chris@0 596 noff.pitch = onEv.data.note.note;
Chris@0 597 noff.frame = pliFrame + duration;
Chris@0 598 noteOffs.insert(noff);
Chris@0 599 }
Chris@0 600
Chris@0 601 while (noteOffs.begin() != noteOffs.end() &&
Chris@0 602 noteOffs.begin()->frame <=
Chris@0 603 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) {
Chris@0 604
Chris@0 605 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
Chris@0 606 (noteOffs.begin()->frame, m_sourceSampleRate);
Chris@0 607
Chris@0 608 offEv.data.note.note = noteOffs.begin()->pitch;
Chris@0 609
Chris@0 610 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 611 std::cerr << "mixModel [sparse]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl;
Chris@0 612 #endif
Chris@0 613
Chris@0 614 plugin->sendEvent(eventTime, &offEv);
Chris@0 615 noteOffs.erase(noteOffs.begin());
Chris@0 616 }
Chris@0 617
Chris@0 618 plugin->run(blockTime);
Chris@0 619 float **outs = plugin->getAudioOutputBuffers();
Chris@0 620
Chris@0 621 for (size_t c = 0; c < m_targetChannelCount; ++c) {
Chris@0 622 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 623 std::cout << "mixModel [sparse]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl;
Chris@0 624 #endif
Chris@0 625
Chris@0 626 size_t sourceChannel = (c % plugin->getAudioOutputCount());
Chris@0 627
Chris@0 628 float channelGain = gain;
Chris@0 629 if (pan != 0.0) {
Chris@0 630 if (c == 0) {
Chris@0 631 if (pan > 0.0) channelGain *= 1.0 - pan;
Chris@0 632 } else {
Chris@0 633 if (pan < 0.0) channelGain *= pan + 1.0;
Chris@0 634 }
Chris@0 635 }
Chris@0 636
Chris@0 637 for (size_t j = 0; j < m_pluginBlockSize; ++j) {
Chris@0 638 buffer[c][i * m_pluginBlockSize + j] +=
Chris@0 639 channelGain * outs[sourceChannel][j];
Chris@0 640 }
Chris@0 641 }
Chris@0 642 }
Chris@0 643
Chris@0 644 return got;
Chris@0 645 }
Chris@0 646
Chris@0 647
Chris@0 648 //!!! mucho duplication with above -- refactor
Chris@0 649 size_t
Chris@0 650 AudioGenerator::mixNoteModel(NoteModel *nm,
Chris@0 651 size_t startFrame, size_t frames,
Chris@0 652 float **buffer, float gain, float pan,
Chris@0 653 size_t /* fadeIn */,
Chris@0 654 size_t /* fadeOut */)
Chris@0 655 {
Chris@0 656 RealTimePluginInstance *plugin = m_synthMap[nm];
Chris@0 657 if (!plugin) return 0;
Chris@0 658
Chris@0 659 size_t latency = plugin->getLatency();
Chris@0 660 size_t blocks = frames / m_pluginBlockSize;
Chris@0 661
Chris@0 662 //!!! hang on -- the fact that the audio callback play source's
Chris@0 663 //buffer is a multiple of the plugin's buffer size doesn't mean
Chris@0 664 //that we always get called for a multiple of it here (because it
Chris@0 665 //also depends on the JACK block size). how should we ensure that
Chris@0 666 //all models write the same amount in to the mix, and that we
Chris@0 667 //always have a multiple of the plugin buffer size? I guess this
Chris@0 668 //class has to be queryable for the plugin buffer size & the
Chris@0 669 //callback play source has to use that as a multiple for all the
Chris@0 670 //calls to mixModel
Chris@0 671
Chris@0 672 size_t got = blocks * m_pluginBlockSize;
Chris@0 673
Chris@0 674 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 675 std::cout << "mixModel [note]: frames " << frames
Chris@0 676 << ", blocks " << blocks << std::endl;
Chris@0 677 #endif
Chris@0 678
Chris@0 679 snd_seq_event_t onEv;
Chris@0 680 onEv.type = SND_SEQ_EVENT_NOTEON;
Chris@0 681 onEv.data.note.channel = 0;
Chris@0 682 onEv.data.note.note = 64;
Chris@185 683 onEv.data.note.velocity = 100;
Chris@0 684
Chris@0 685 snd_seq_event_t offEv;
Chris@0 686 offEv.type = SND_SEQ_EVENT_NOTEOFF;
Chris@0 687 offEv.data.note.channel = 0;
Chris@0 688 offEv.data.note.velocity = 0;
Chris@0 689
Chris@0 690 NoteOffSet &noteOffs = m_noteOffs[nm];
Chris@0 691
Chris@0 692 for (size_t i = 0; i < blocks; ++i) {
Chris@0 693
Chris@0 694 size_t reqStart = startFrame + i * m_pluginBlockSize;
Chris@0 695
Chris@0 696 NoteModel::PointList points =
Chris@0 697 nm->getPoints(reqStart + latency,
Chris@0 698 reqStart + latency + m_pluginBlockSize);
Chris@0 699
Chris@0 700 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime
Chris@0 701 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate);
Chris@0 702
Chris@0 703 for (NoteModel::PointList::iterator pli =
Chris@0 704 points.begin(); pli != points.end(); ++pli) {
Chris@0 705
Chris@0 706 size_t pliFrame = pli->frame;
Chris@0 707
Chris@0 708 if (pliFrame >= latency) pliFrame -= latency;
Chris@0 709
Chris@0 710 if (pliFrame < reqStart ||
Chris@0 711 pliFrame >= reqStart + m_pluginBlockSize) continue;
Chris@0 712
Chris@0 713 while (noteOffs.begin() != noteOffs.end() &&
Chris@0 714 noteOffs.begin()->frame <= pliFrame) {
Chris@0 715
Chris@0 716 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
Chris@0 717 (noteOffs.begin()->frame, m_sourceSampleRate);
Chris@0 718
Chris@0 719 offEv.data.note.note = noteOffs.begin()->pitch;
Chris@0 720
Chris@0 721 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 722 std::cerr << "mixModel [note]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl;
Chris@0 723 #endif
Chris@0 724
Chris@0 725 plugin->sendEvent(eventTime, &offEv);
Chris@0 726 noteOffs.erase(noteOffs.begin());
Chris@0 727 }
Chris@0 728
Chris@0 729 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
Chris@0 730 (pliFrame, m_sourceSampleRate);
Chris@0 731
Chris@0 732 if (nm->getScaleUnits() == "Hz") {
Chris@0 733 onEv.data.note.note = Pitch::getPitchForFrequency(pli->value);
Chris@0 734 } else {
Chris@0 735 onEv.data.note.note = lrintf(pli->value);
Chris@0 736 }
Chris@0 737
Chris@0 738 plugin->sendEvent(eventTime, &onEv);
Chris@0 739
Chris@0 740 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 741 std::cout << "mixModel [note]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl;
Chris@0 742 #endif
Chris@0 743
Chris@0 744 size_t duration = pli->duration;
Chris@0 745 if (duration == 0 || duration == 1) {
Chris@0 746 duration = m_sourceSampleRate / 20;
Chris@0 747 }
Chris@0 748 NoteOff noff;
Chris@0 749 noff.pitch = onEv.data.note.note;
Chris@0 750 noff.frame = pliFrame + duration;
Chris@0 751 noteOffs.insert(noff);
Chris@0 752 }
Chris@0 753
Chris@0 754 while (noteOffs.begin() != noteOffs.end() &&
Chris@0 755 noteOffs.begin()->frame <=
Chris@0 756 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) {
Chris@0 757
Chris@0 758 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
Chris@0 759 (noteOffs.begin()->frame, m_sourceSampleRate);
Chris@0 760
Chris@0 761 offEv.data.note.note = noteOffs.begin()->pitch;
Chris@0 762
Chris@0 763 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 764 std::cerr << "mixModel [note]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl;
Chris@0 765 #endif
Chris@0 766
Chris@0 767 plugin->sendEvent(eventTime, &offEv);
Chris@0 768 noteOffs.erase(noteOffs.begin());
Chris@0 769 }
Chris@0 770
Chris@0 771 plugin->run(blockTime);
Chris@0 772 float **outs = plugin->getAudioOutputBuffers();
Chris@0 773
Chris@0 774 for (size_t c = 0; c < m_targetChannelCount; ++c) {
Chris@0 775 #ifdef DEBUG_AUDIO_GENERATOR
Chris@0 776 std::cout << "mixModel [note]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl;
Chris@0 777 #endif
Chris@0 778
Chris@0 779 size_t sourceChannel = (c % plugin->getAudioOutputCount());
Chris@0 780
Chris@0 781 float channelGain = gain;
Chris@0 782 if (pan != 0.0) {
Chris@0 783 if (c == 0) {
Chris@0 784 if (pan > 0.0) channelGain *= 1.0 - pan;
Chris@0 785 } else {
Chris@0 786 if (pan < 0.0) channelGain *= pan + 1.0;
Chris@0 787 }
Chris@0 788 }
Chris@0 789
Chris@0 790 for (size_t j = 0; j < m_pluginBlockSize; ++j) {
Chris@0 791 buffer[c][i * m_pluginBlockSize + j] +=
Chris@0 792 channelGain * outs[sourceChannel][j];
Chris@0 793 }
Chris@0 794 }
Chris@0 795 }
Chris@0 796
Chris@0 797 return got;
Chris@0 798 }
Chris@0 799