annotate audioio/AudioGenerator.cpp @ 45:6b6bca31ad53

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