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