# HG changeset patch # User Chris Cannam # Date 1389186442 0 # Node ID 58582119c92a2c23d08cd8847967e3e7f8ec8f27 # Parent faee60602049197000dc56f8bccec93c9772f113 Add a basic continuous synth implementation (simple sinusoids only, no gaps) diff -r faee60602049 -r 58582119c92a audioio/AudioGenerator.cpp --- a/audioio/AudioGenerator.cpp Wed Jan 08 11:00:12 2014 +0000 +++ b/audioio/AudioGenerator.cpp Wed Jan 08 13:07:22 2014 +0000 @@ -24,10 +24,12 @@ #include "data/model/NoteModel.h" #include "data/model/FlexiNoteModel.h" #include "data/model/DenseTimeValueModel.h" +#include "data/model/SparseTimeValueModel.h" #include "data/model/SparseOneDimensionalModel.h" #include "data/model/NoteData.h" #include "ClipMixer.h" +#include "ContinuousSynth.h" #include #include @@ -118,11 +120,22 @@ } } - ClipMixer *mixer = makeClipMixerFor(model); - if (mixer) { - QMutexLocker locker(&m_mutex); - m_clipMixerMap[model] = mixer; - return true; + if (usesClipMixer(model)) { + ClipMixer *mixer = makeClipMixerFor(model); + if (mixer) { + QMutexLocker locker(&m_mutex); + m_clipMixerMap[model] = mixer; + return true; + } + } + + if (usesContinuousSynth(model)) { + ContinuousSynth *synth = makeSynthFor(model); + if (synth) { + QMutexLocker locker(&m_mutex); + m_continuousSynthMap[model] = synth; + return true; + } } return false; @@ -148,40 +161,24 @@ } } -/*!!! -void -AudioGenerator::playPluginConfigurationChanged(const Playable *playable, - QString configurationXml) +bool +AudioGenerator::usesClipMixer(const Model *model) { -// SVDEBUG << "AudioGenerator::playPluginConfigurationChanged" << endl; - - const Model *model = dynamic_cast(playable); - if (!model) { - cerr << "WARNING: AudioGenerator::playClipIdChanged: playable " - << playable << " is not a supported model type" - << endl; - return; - } - - if (m_synthMap.find(model) == m_synthMap.end()) { - SVDEBUG << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << endl; - return; - } - - RealTimePluginInstance *plugin = m_synthMap[model]; - if (plugin) { - PluginXml(plugin).setParametersFromXml(configurationXml); - } + bool clip = + (qobject_cast(model) || + qobject_cast(model) || + qobject_cast(model)); + return clip; } -void -AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) +bool +AudioGenerator::usesContinuousSynth(const Model *model) { - if (m_sampleDir != "") { - plugin->configure("sampledir", m_sampleDir.toStdString()); - } -} -*/ + bool cont = + (qobject_cast(model)); + return cont; +} + ClipMixer * AudioGenerator::makeClipMixerFor(const Model *model) { @@ -221,6 +218,21 @@ return mixer; } +ContinuousSynth * +AudioGenerator::makeSynthFor(const Model *model) +{ + const Playable *playable = model; + if (!playable || !playable->canPlay()) return 0; + + ContinuousSynth *synth = new ContinuousSynth(m_targetChannelCount, + m_sourceSampleRate, + m_processingBlockSize); + + std::cerr << "AudioGenerator::makeSynthFor(" << model << "): created synth" << std::endl; + + return synth; +} + void AudioGenerator::removeModel(Model *model) { @@ -346,14 +358,14 @@ buffer, gain, pan, fadeIn, fadeOut); } - bool synthetic = - (qobject_cast(model) || - qobject_cast(model) || - qobject_cast(model)); + if (usesClipMixer(model)) { + return mixClipModel(model, startFrame, frameCount, + buffer, gain, pan); + } - if (synthetic) { - return mixSyntheticNoteModel(model, startFrame, frameCount, - buffer, gain, pan, fadeIn, fadeOut); + if (usesContinuousSynth(model)) { + return mixContinuousSynthModel(model, startFrame, frameCount, + buffer, gain, pan); } 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; @@ -457,17 +469,17 @@ } size_t -AudioGenerator::mixSyntheticNoteModel(Model *model, - size_t startFrame, size_t frames, - float **buffer, float gain, float pan, - size_t /* fadeIn */, - size_t /* fadeOut */) +AudioGenerator::mixClipModel(Model *model, + size_t startFrame, size_t frames, + float **buffer, float gain, float pan) { ClipMixer *clipMixer = m_clipMixerMap[model]; if (!clipMixer) return 0; size_t blocks = frames / m_processingBlockSize; + //!!! todo: the below -- it matters + //!!! hang on -- the fact that the audio callback play source's //buffer is a multiple of the plugin's buffer size doesn't mean //that we always get called for a multiple of it here (because it @@ -481,7 +493,7 @@ size_t got = blocks * m_processingBlockSize; #ifdef DEBUG_AUDIO_GENERATOR - cout << "mixModel [synthetic note]: frames " << frames + cout << "mixModel [clip]: frames " << frames << ", blocks " << blocks << endl; #endif @@ -524,7 +536,7 @@ off.frequency = noteOffs.begin()->frequency; #ifdef DEBUG_AUDIO_GENERATOR - cerr << "mixModel [synthetic]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; + cerr << "mixModel [clip]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; #endif ends.push_back(off); @@ -537,7 +549,7 @@ on.pan = pan; #ifdef DEBUG_AUDIO_GENERATOR - cout << "mixModel [synthetic]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << endl; + cout << "mixModel [clip]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << endl; #endif starts.push_back(on); @@ -555,7 +567,7 @@ off.frequency = noteOffs.begin()->frequency; #ifdef DEBUG_AUDIO_GENERATOR - cerr << "mixModel [synthetic]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; + cerr << "mixModel [clip]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; #endif ends.push_back(off); @@ -573,3 +585,70 @@ return got; } + +size_t +AudioGenerator::mixContinuousSynthModel(Model *model, + size_t startFrame, + size_t frames, + float **buffer, + float gain, + float pan) +{ + ContinuousSynth *synth = m_continuousSynthMap[model]; + if (!synth) return 0; + + // only type we support here at the moment + SparseTimeValueModel *stvm = qobject_cast(model); + if (stvm->getScaleUnits() != "Hz") return 0; + + size_t blocks = frames / m_processingBlockSize; + + //!!! todo: see comment in mixClipModel + + size_t got = blocks * m_processingBlockSize; + +#ifdef DEBUG_AUDIO_GENERATOR + cout << "mixModel [synth]: frames " << frames + << ", blocks " << blocks << endl; +#endif + + float **bufferIndexes = new float *[m_targetChannelCount]; + + for (size_t i = 0; i < blocks; ++i) { + + size_t reqStart = startFrame + i * m_processingBlockSize; + + for (size_t c = 0; c < m_targetChannelCount; ++c) { + bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; + } + + SparseTimeValueModel::PointList points = + stvm->getPoints(reqStart, reqStart + m_processingBlockSize); + + // by default, repeat last frequency + float f0 = 0.f; + + // go straight to the last freq that is genuinely in this range + for (SparseTimeValueModel::PointList::const_iterator itr = points.end(); + itr != points.begin(); ) { + --itr; + if (itr->frame >= reqStart && + itr->frame < reqStart + m_processingBlockSize) { + f0 = itr->value; + break; + } + } + + cerr << "f0 = " << f0 << endl; + + synth->mix(bufferIndexes, + gain, + pan, + f0); + } + + delete[] bufferIndexes; + + return got; +} + diff -r faee60602049 -r 58582119c92a audioio/AudioGenerator.h --- a/audioio/AudioGenerator.h Wed Jan 08 11:00:12 2014 +0000 +++ b/audioio/AudioGenerator.h Wed Jan 08 13:07:22 2014 +0000 @@ -23,6 +23,7 @@ class SparseOneDimensionalModel; class Playable; class ClipMixer; +class ContinuousSynth; #include #include @@ -122,12 +123,21 @@ typedef std::multiset NoteOffSet; typedef std::map NoteOffMap; + typedef std::map ContinuousSynthMap; + QMutex m_mutex; + ClipMixerMap m_clipMixerMap; NoteOffMap m_noteOffs; static QString m_sampleDir; + ContinuousSynthMap m_continuousSynthMap; + + bool usesClipMixer(const Model *); + bool usesContinuousSynth(const Model *); + ClipMixer *makeClipMixerFor(const Model *model); + ContinuousSynth *makeSynthFor(const Model *model); static void initialiseSampleDir(); @@ -135,9 +145,13 @@ (DenseTimeValueModel *model, size_t startFrame, size_t frameCount, float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); - virtual size_t mixSyntheticNoteModel + virtual size_t mixClipModel (Model *model, size_t startFrame, size_t frameCount, - float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); + float **buffer, float gain, float pan); + + virtual size_t mixContinuousSynthModel + (Model *model, size_t startFrame, size_t frameCount, + float **buffer, float gain, float pan); static const size_t m_processingBlockSize; }; diff -r faee60602049 -r 58582119c92a audioio/ClipMixer.cpp --- a/audioio/ClipMixer.cpp Wed Jan 08 11:00:12 2014 +0000 +++ b/audioio/ClipMixer.cpp Wed Jan 08 13:07:22 2014 +0000 @@ -178,6 +178,8 @@ } } + delete[] levels; + m_playing = remaining; } diff -r faee60602049 -r 58582119c92a audioio/ContinuousSynth.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audioio/ContinuousSynth.cpp Wed Jan 08 13:07:22 2014 +0000 @@ -0,0 +1,93 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ContinuousSynth.h" + +#include "base/Debug.h" + +#include + +ContinuousSynth::ContinuousSynth(int channels, int sampleRate, int blockSize) : + m_channels(channels), + m_sampleRate(sampleRate), + m_blockSize(blockSize), + m_prevF0(-1.f), + m_phase(0.0) +{ +} + +ContinuousSynth::~ContinuousSynth() +{ +} + +void +ContinuousSynth::reset() +{ + m_phase = 0; +} + +void +ContinuousSynth::mix(float **toBuffers, float gain, float pan, float f0) +{ + if (f0 == 0.f) f0 = m_prevF0; + + bool wasOn = (m_prevF0 > 0.f); + bool nowOn = (f0 > 0.f); + + if (!nowOn && !wasOn) { + m_phase = 0; + return; + } + + int fadeLength = 20; // samples + + float *levels = new float[m_channels]; + + for (int c = 0; c < m_channels; ++c) { + levels[c] = gain; + } + if (pan != 0.0 && m_channels == 2) { + levels[0] *= 1.0 - pan; + levels[1] *= pan + 1.0; + } + + double phasor = (f0 * 2 * M_PI) / m_sampleRate; + double p = m_phase; + + cerr << "ContinuousSynth::mix: f0 = " << f0 << " (from " << m_prevF0 << "), phase = " << m_phase << ", phasor = " << phasor << endl; + + for (int i = 0; i < m_blockSize; ++i) { + + p = m_phase + i * phasor; + + double v = sin(p); + + if (!wasOn && i < fadeLength) { // fade in + v = v * (i / double(fadeLength)); + } else if (!nowOn) { + if (i > fadeLength) v = 0; + else v = v * (1.0 - (i / double(fadeLength))); + } + + for (int c = 0; c < m_channels; ++c) { + toBuffers[c][i] += levels[c] * v; + } + } + + m_prevF0 = f0; + m_phase = p; + + delete[] levels; +} + diff -r faee60602049 -r 58582119c92a audioio/ContinuousSynth.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audioio/ContinuousSynth.h Wed Jan 08 13:07:22 2014 +0000 @@ -0,0 +1,61 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CONTINUOUS_SYNTH_H +#define CONTINUOUS_SYNTH_H + +/** + * Mix into a target buffer a signal synthesised so as to sound at a + * specific frequency. The frequency may change with each processing + * block, or may be switched on or off. + */ + +class ContinuousSynth +{ +public: + ContinuousSynth(int channels, int sampleRate, int blockSize); + ~ContinuousSynth(); + + void setChannelCount(int channels); + + void reset(); + + /** + * Mix in a signal to be heard at the given fundamental + * frequency. Any oscillator state will be maintained between + * process calls so as to provide a continuous sound. The f0 value + * may vary between calls. + * + * Supply f0 equal to 0 if you want to maintain the f0 from the + * previous block (without having to remember what it was). + * + * Supply f0 less than 0 for silence. You should continue to call + * this even when the signal is silent if you want to ensure the + * sound switches on and off cleanly. + */ + void mix(float **toBuffers, + float gain, + float pan, + float f0); + +private: + int m_channels; + int m_sampleRate; + int m_blockSize; + + double m_prevF0; + double m_phase; +}; + +#endif diff -r faee60602049 -r 58582119c92a svapp.pro --- a/svapp.pro Wed Jan 08 11:00:12 2014 +0000 +++ b/svapp.pro Wed Jan 08 13:07:22 2014 +0000 @@ -36,6 +36,7 @@ audioio/AudioPulseAudioTarget.h \ audioio/AudioTargetFactory.h \ audioio/ClipMixer.h \ + audioio/ContinuousSynth.h \ audioio/PlaySpeedRangeMapper.h SOURCES += audioio/AudioCallbackPlaySource.cpp \ @@ -47,6 +48,7 @@ audioio/AudioPulseAudioTarget.cpp \ audioio/AudioTargetFactory.cpp \ audioio/ClipMixer.cpp \ + audioio/ContinuousSynth.cpp \ audioio/PlaySpeedRangeMapper.cpp HEADERS += framework/Document.h \