Mercurial > hg > svapp
changeset 42:0619006a1ee3 last-cc-copyright sv1-1.0pre1 sv1-1.0pre2 sv1-1.0pre3 sv1-1.0pre4 sv1-1.0rc1 sv1-v1.0
* Reorganising code base. This revision will not compile.
author | Chris Cannam |
---|---|
date | Mon, 31 Jul 2006 12:03:45 +0000 |
parents | a4a05344c7d6 |
children | 3c5756fb6a68 |
files | audioio/AudioCallbackPlaySource.cpp audioio/AudioCallbackPlaySource.h audioio/AudioCallbackPlayTarget.cpp audioio/AudioCallbackPlayTarget.h audioio/AudioCoreAudioTarget.cpp audioio/AudioCoreAudioTarget.h audioio/AudioGenerator.cpp audioio/AudioGenerator.h audioio/AudioJACKTarget.cpp audioio/AudioJACKTarget.h audioio/AudioPortAudioTarget.cpp audioio/AudioPortAudioTarget.h audioio/AudioTargetFactory.cpp audioio/AudioTargetFactory.h audioio/IntegerTimeStretcher.cpp audioio/IntegerTimeStretcher.h |
diffstat | 16 files changed, 0 insertions(+), 3612 deletions(-) [+] |
line wrap: on
line diff
--- a/audioio/AudioCallbackPlaySource.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1257 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 "AudioCallbackPlaySource.h" - -#include "AudioGenerator.h" - -#include "base/Model.h" -#include "base/ViewManager.h" -#include "base/PlayParameterRepository.h" -#include "model/DenseTimeValueModel.h" -#include "model/SparseOneDimensionalModel.h" -#include "IntegerTimeStretcher.h" - -#include <iostream> -#include <cassert> - -//#define DEBUG_AUDIO_PLAY_SOURCE 1 -//#define DEBUG_AUDIO_PLAY_SOURCE_PLAYING 1 - -//const size_t AudioCallbackPlaySource::m_ringBufferSize = 102400; -const size_t AudioCallbackPlaySource::m_ringBufferSize = 131071; - -AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManager *manager) : - m_viewManager(manager), - m_audioGenerator(new AudioGenerator()), - m_readBuffers(0), - m_writeBuffers(0), - m_readBufferFill(0), - m_writeBufferFill(0), - m_bufferScavenger(1), - m_sourceChannelCount(0), - m_blockSize(1024), - m_sourceSampleRate(0), - m_targetSampleRate(0), - m_playLatency(0), - m_playing(false), - m_exiting(false), - m_lastModelEndFrame(0), - m_outputLeft(0.0), - m_outputRight(0.0), - m_slowdownCounter(0), - m_timeStretcher(0), - m_fillThread(0), - m_converter(0) -{ - m_viewManager->setAudioPlaySource(this); - - connect(m_viewManager, SIGNAL(selectionChanged()), - this, SLOT(selectionChanged())); - connect(m_viewManager, SIGNAL(playLoopModeChanged()), - this, SLOT(playLoopModeChanged())); - connect(m_viewManager, SIGNAL(playSelectionModeChanged()), - this, SLOT(playSelectionModeChanged())); - - connect(PlayParameterRepository::getInstance(), - SIGNAL(playParametersChanged(PlayParameters *)), - this, SLOT(playParametersChanged(PlayParameters *))); -} - -AudioCallbackPlaySource::~AudioCallbackPlaySource() -{ - m_exiting = true; - - if (m_fillThread) { - m_condition.wakeAll(); - m_fillThread->wait(); - delete m_fillThread; - } - - clearModels(); - - if (m_readBuffers != m_writeBuffers) { - delete m_readBuffers; - } - - delete m_writeBuffers; - - delete m_audioGenerator; - - m_bufferScavenger.scavenge(true); -} - -void -AudioCallbackPlaySource::addModel(Model *model) -{ - if (m_models.find(model) != m_models.end()) return; - - bool canPlay = m_audioGenerator->addModel(model); - - m_mutex.lock(); - - m_models.insert(model); - if (model->getEndFrame() > m_lastModelEndFrame) { - m_lastModelEndFrame = model->getEndFrame(); - } - - bool buffersChanged = false, srChanged = false; - - size_t modelChannels = 1; - DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model); - if (dtvm) modelChannels = dtvm->getChannelCount(); - if (modelChannels > m_sourceChannelCount) { - m_sourceChannelCount = modelChannels; - } - -// std::cerr << "Adding model with " << modelChannels << " channels " << std::endl; - - if (m_sourceSampleRate == 0) { - - m_sourceSampleRate = model->getSampleRate(); - srChanged = true; - - } else if (model->getSampleRate() != m_sourceSampleRate) { - - // If this is a dense time-value model and we have no other, we - // can just switch to this model's sample rate - - if (dtvm) { - - bool conflicting = false; - - for (std::set<Model *>::const_iterator i = m_models.begin(); - i != m_models.end(); ++i) { - if (*i != dtvm && dynamic_cast<DenseTimeValueModel *>(*i)) { - std::cerr << "AudioCallbackPlaySource::addModel: Conflicting dense time-value model " << *i << " found" << std::endl; - conflicting = true; - break; - } - } - - if (conflicting) { - - std::cerr << "AudioCallbackPlaySource::addModel: ERROR: " - << "New model sample rate does not match" << std::endl - << "existing model(s) (new " << model->getSampleRate() - << " vs " << m_sourceSampleRate - << "), playback will be wrong" - << std::endl; - - emit sampleRateMismatch(model->getSampleRate(), m_sourceSampleRate, - false); - } else { - m_sourceSampleRate = model->getSampleRate(); - srChanged = true; - } - } - } - - if (!m_writeBuffers || (m_writeBuffers->size() < getTargetChannelCount())) { - clearRingBuffers(true, getTargetChannelCount()); - buffersChanged = true; - } else { - if (canPlay) clearRingBuffers(true); - } - - if (buffersChanged || srChanged) { - if (m_converter) { - src_delete(m_converter); - m_converter = 0; - } - } - - m_mutex.unlock(); - - m_audioGenerator->setTargetChannelCount(getTargetChannelCount()); - - if (!m_fillThread) { - m_fillThread = new AudioCallbackPlaySourceFillThread(*this); - m_fillThread->start(); - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "AudioCallbackPlaySource::addModel: emitting modelReplaced" << std::endl; -#endif - - if (buffersChanged || srChanged) { - emit modelReplaced(); - } - - m_condition.wakeAll(); -} - -void -AudioCallbackPlaySource::removeModel(Model *model) -{ - m_mutex.lock(); - - m_models.erase(model); - - if (m_models.empty()) { - if (m_converter) { - src_delete(m_converter); - m_converter = 0; - } - m_sourceSampleRate = 0; - } - - size_t lastEnd = 0; - for (std::set<Model *>::const_iterator i = m_models.begin(); - i != m_models.end(); ++i) { -// std::cerr << "AudioCallbackPlaySource::removeModel(" << model << "): checking end frame on model " << *i << std::endl; - if ((*i)->getEndFrame() > lastEnd) lastEnd = (*i)->getEndFrame(); -// std::cerr << "(done, lastEnd now " << lastEnd << ")" << std::endl; - } - m_lastModelEndFrame = lastEnd; - - m_mutex.unlock(); - - m_audioGenerator->removeModel(model); - - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::clearModels() -{ - m_mutex.lock(); - - m_models.clear(); - - if (m_converter) { - src_delete(m_converter); - m_converter = 0; - } - - m_lastModelEndFrame = 0; - - m_sourceSampleRate = 0; - - m_mutex.unlock(); - - m_audioGenerator->clearModels(); -} - -void -AudioCallbackPlaySource::clearRingBuffers(bool haveLock, size_t count) -{ - if (!haveLock) m_mutex.lock(); - - if (count == 0) { - if (m_writeBuffers) count = m_writeBuffers->size(); - } - - size_t sf = m_readBufferFill; - RingBuffer<float> *rb = getReadRingBuffer(0); - if (rb) { - //!!! This is incorrect if we're in a non-contiguous selection - //Same goes for all related code (subtracting the read space - //from the fill frame to try to establish where the effective - //pre-resample/timestretch read pointer is) - size_t rs = rb->getReadSpace(); - if (rs < sf) sf -= rs; - else sf = 0; - } - m_writeBufferFill = sf; - - if (m_readBuffers != m_writeBuffers) { - delete m_writeBuffers; - } - - m_writeBuffers = new RingBufferVector; - - for (size_t i = 0; i < count; ++i) { - m_writeBuffers->push_back(new RingBuffer<float>(m_ringBufferSize)); - } - -// std::cerr << "AudioCallbackPlaySource::clearRingBuffers: Created " -// << count << " write buffers" << std::endl; - - if (!haveLock) { - m_mutex.unlock(); - } -} - -void -AudioCallbackPlaySource::play(size_t startFrame) -{ - if (m_viewManager->getPlaySelectionMode() && - !m_viewManager->getSelections().empty()) { - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - MultiSelection::SelectionList::iterator i = selections.begin(); - if (i != selections.end()) { - if (startFrame < i->getStartFrame()) { - startFrame = i->getStartFrame(); - } else { - MultiSelection::SelectionList::iterator j = selections.end(); - --j; - if (startFrame >= j->getEndFrame()) { - startFrame = i->getStartFrame(); - } - } - } - } else { - if (startFrame >= m_lastModelEndFrame) { - startFrame = 0; - } - } - - // The fill thread will automatically empty its buffers before - // starting again if we have not so far been playing, but not if - // we're just re-seeking. - - m_mutex.lock(); - if (m_playing) { - m_readBufferFill = m_writeBufferFill = startFrame; - if (m_readBuffers) { - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer<float> *rb = getReadRingBuffer(c); - if (rb) rb->reset(); - } - } - if (m_converter) src_reset(m_converter); - } else { - if (m_converter) src_reset(m_converter); - m_readBufferFill = m_writeBufferFill = startFrame; - } - m_mutex.unlock(); - - m_audioGenerator->reset(); - - bool changed = !m_playing; - m_playing = true; - m_condition.wakeAll(); - if (changed) emit playStatusChanged(m_playing); -} - -void -AudioCallbackPlaySource::stop() -{ - bool changed = m_playing; - m_playing = false; - m_condition.wakeAll(); - if (changed) emit playStatusChanged(m_playing); -} - -void -AudioCallbackPlaySource::selectionChanged() -{ - if (m_viewManager->getPlaySelectionMode()) { - clearRingBuffers(); - } -} - -void -AudioCallbackPlaySource::playLoopModeChanged() -{ - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::playSelectionModeChanged() -{ - if (!m_viewManager->getSelections().empty()) { - clearRingBuffers(); - } -} - -void -AudioCallbackPlaySource::playParametersChanged(PlayParameters *params) -{ - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::setTargetBlockSize(size_t size) -{ -// std::cerr << "AudioCallbackPlaySource::setTargetBlockSize() -> " << size << std::endl; - assert(size < m_ringBufferSize); - m_blockSize = size; -} - -size_t -AudioCallbackPlaySource::getTargetBlockSize() const -{ -// std::cerr << "AudioCallbackPlaySource::getTargetBlockSize() -> " << m_blockSize << std::endl; - return m_blockSize; -} - -void -AudioCallbackPlaySource::setTargetPlayLatency(size_t latency) -{ - m_playLatency = latency; -} - -size_t -AudioCallbackPlaySource::getTargetPlayLatency() const -{ - return m_playLatency; -} - -size_t -AudioCallbackPlaySource::getCurrentPlayingFrame() -{ - bool resample = false; - double ratio = 1.0; - - if (getSourceSampleRate() != getTargetSampleRate()) { - resample = true; - ratio = double(getSourceSampleRate()) / double(getTargetSampleRate()); - } - - size_t readSpace = 0; - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer<float> *rb = getReadRingBuffer(c); - if (rb) { - size_t spaceHere = rb->getReadSpace(); - if (c == 0 || spaceHere < readSpace) readSpace = spaceHere; - } - } - - if (resample) { - readSpace = size_t(readSpace * ratio + 0.1); - } - - size_t latency = m_playLatency; - if (resample) latency = size_t(m_playLatency * ratio + 0.1); - - TimeStretcherData *timeStretcher = m_timeStretcher; - if (timeStretcher) { - latency += timeStretcher->getStretcher(0)->getProcessingLatency(); - } - - latency += readSpace; - size_t bufferedFrame = m_readBufferFill; - - bool looping = m_viewManager->getPlayLoopMode(); - bool constrained = (m_viewManager->getPlaySelectionMode() && - !m_viewManager->getSelections().empty()); - - size_t framePlaying = bufferedFrame; - - if (looping && !constrained) { - while (framePlaying < latency) framePlaying += m_lastModelEndFrame; - } - - if (framePlaying > latency) framePlaying -= latency; - else framePlaying = 0; - - if (!constrained) { - if (!looping && framePlaying > m_lastModelEndFrame) { - framePlaying = m_lastModelEndFrame; - stop(); - } - return framePlaying; - } - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - MultiSelection::SelectionList::const_iterator i; - - i = selections.begin(); - size_t rangeStart = i->getStartFrame(); - - i = selections.end(); - --i; - size_t rangeEnd = i->getEndFrame(); - - for (i = selections.begin(); i != selections.end(); ++i) { - if (i->contains(bufferedFrame)) break; - } - - size_t f = bufferedFrame; - -// std::cerr << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << ", rangeEnd=" << rangeEnd << std::endl; - - if (i == selections.end()) { - --i; - if (i->getEndFrame() + latency < f) { -// std::cerr << "framePlaying = " << framePlaying << ", rangeEnd = " << rangeEnd << std::endl; - - if (!looping && (framePlaying > rangeEnd)) { -// std::cerr << "STOPPING" << std::endl; - stop(); - return rangeEnd; - } else { - return framePlaying; - } - } else { -// std::cerr << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl; - latency -= (f - i->getEndFrame()); - f = i->getEndFrame(); - } - } - -// std::cerr << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl; - - while (latency > 0) { - size_t offset = f - i->getStartFrame(); - if (offset >= latency) { - if (f > latency) { - framePlaying = f - latency; - } else { - framePlaying = 0; - } - break; - } else { - if (i == selections.begin()) { - if (looping) { - i = selections.end(); - } - } - latency -= offset; - --i; - f = i->getEndFrame(); - } - } - - return framePlaying; -} - -void -AudioCallbackPlaySource::setOutputLevels(float left, float right) -{ - m_outputLeft = left; - m_outputRight = right; -} - -bool -AudioCallbackPlaySource::getOutputLevels(float &left, float &right) -{ - left = m_outputLeft; - right = m_outputRight; - return true; -} - -void -AudioCallbackPlaySource::setTargetSampleRate(size_t sr) -{ - m_targetSampleRate = sr; - - if (getSourceSampleRate() != getTargetSampleRate()) { - - int err = 0; - m_converter = src_new(SRC_SINC_BEST_QUALITY, - getTargetChannelCount(), &err); - if (!m_converter) { - std::cerr - << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: " - << src_strerror(err) << std::endl; - - emit sampleRateMismatch(getSourceSampleRate(), - getTargetSampleRate(), - false); - } else { - - emit sampleRateMismatch(getSourceSampleRate(), - getTargetSampleRate(), - true); - } - } -} - -size_t -AudioCallbackPlaySource::getTargetSampleRate() const -{ - if (m_targetSampleRate) return m_targetSampleRate; - else return getSourceSampleRate(); -} - -size_t -AudioCallbackPlaySource::getSourceChannelCount() const -{ - return m_sourceChannelCount; -} - -size_t -AudioCallbackPlaySource::getTargetChannelCount() const -{ - if (m_sourceChannelCount < 2) return 2; - return m_sourceChannelCount; -} - -size_t -AudioCallbackPlaySource::getSourceSampleRate() const -{ - return m_sourceSampleRate; -} - -AudioCallbackPlaySource::TimeStretcherData::TimeStretcherData(size_t channels, - size_t factor, - size_t blockSize) : - m_factor(factor), - m_blockSize(blockSize) -{ -// std::cerr << "TimeStretcherData::TimeStretcherData(" << channels << ", " << factor << ", " << blockSize << ")" << std::endl; - - for (size_t ch = 0; ch < channels; ++ch) { - m_stretcher[ch] = StretcherBuffer - //!!! We really need to measure performance and work out - //what sort of quality level to use -- or at least to - //allow the user to configure it - (new IntegerTimeStretcher(factor, blockSize, 128), - new float[blockSize * factor]); - } - m_stretchInputBuffer = new float[blockSize]; -} - -AudioCallbackPlaySource::TimeStretcherData::~TimeStretcherData() -{ -// std::cerr << "TimeStretcherData::~TimeStretcherData" << std::endl; - - while (!m_stretcher.empty()) { - delete m_stretcher.begin()->second.first; - delete[] m_stretcher.begin()->second.second; - m_stretcher.erase(m_stretcher.begin()); - } - delete m_stretchInputBuffer; -} - -IntegerTimeStretcher * -AudioCallbackPlaySource::TimeStretcherData::getStretcher(size_t channel) -{ - return m_stretcher[channel].first; -} - -float * -AudioCallbackPlaySource::TimeStretcherData::getOutputBuffer(size_t channel) -{ - return m_stretcher[channel].second; -} - -float * -AudioCallbackPlaySource::TimeStretcherData::getInputBuffer() -{ - return m_stretchInputBuffer; -} - -void -AudioCallbackPlaySource::TimeStretcherData::run(size_t channel) -{ - getStretcher(channel)->process(getInputBuffer(), - getOutputBuffer(channel), - m_blockSize); -} - -void -AudioCallbackPlaySource::setSlowdownFactor(size_t factor) -{ - // Avoid locks -- create, assign, mark old one for scavenging - // later (as a call to getSourceSamples may still be using it) - - TimeStretcherData *existingStretcher = m_timeStretcher; - - if (existingStretcher && existingStretcher->getFactor() == factor) { - return; - } - - if (factor > 1) { - TimeStretcherData *newStretcher = new TimeStretcherData - (getTargetChannelCount(), factor, getTargetBlockSize()); - m_slowdownCounter = 0; - m_timeStretcher = newStretcher; - } else { - m_timeStretcher = 0; - } - - if (existingStretcher) { - m_timeStretcherScavenger.claim(existingStretcher); - } -} - -size_t -AudioCallbackPlaySource::getSourceSamples(size_t count, float **buffer) -{ - if (!m_playing) { - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - for (size_t i = 0; i < count; ++i) { - buffer[ch][i] = 0.0; - } - } - return 0; - } - - TimeStretcherData *timeStretcher = m_timeStretcher; - - if (!timeStretcher || timeStretcher->getFactor() == 1) { - - size_t got = 0; - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - - RingBuffer<float> *rb = getReadRingBuffer(ch); - - if (rb) { - - // this is marginally more likely to leave our channels in - // sync after a processing failure than just passing "count": - size_t request = count; - if (ch > 0) request = got; - - got = rb->read(buffer[ch], request); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", signalling for more (possibly)" << std::endl; -#endif - } - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - for (size_t i = got; i < count; ++i) { - buffer[ch][i] = 0.0; - } - } - } - - m_condition.wakeAll(); - return got; - } - - if (m_slowdownCounter == 0) { - - size_t got = 0; - float *ib = timeStretcher->getInputBuffer(); - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - - RingBuffer<float> *rb = getReadRingBuffer(ch); - - if (rb) { - - size_t request = count; - if (ch > 0) request = got; // see above - got = rb->read(buffer[ch], request); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", running time stretcher" << std::endl; -#endif - - for (size_t i = 0; i < count; ++i) { - ib[i] = buffer[ch][i]; - } - - timeStretcher->run(ch); - } - } - - } else if (m_slowdownCounter >= timeStretcher->getFactor()) { - // reset this in case the factor has changed leaving the - // counter out of range - m_slowdownCounter = 0; - } - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - - float *ob = timeStretcher->getOutputBuffer(ch); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "AudioCallbackPlaySource::getSamples: Copying from (" << (m_slowdownCounter * count) << "," << count << ") to buffer" << std::endl; -#endif - - for (size_t i = 0; i < count; ++i) { - buffer[ch][i] = ob[m_slowdownCounter * count + i]; - } - } - -//!!! if (m_slowdownCounter == 0) m_condition.wakeAll(); - m_slowdownCounter = (m_slowdownCounter + 1) % timeStretcher->getFactor(); - return count; -} - -// Called from fill thread, m_playing true, mutex held -bool -AudioCallbackPlaySource::fillBuffers() -{ - static float *tmp = 0; - static size_t tmpSize = 0; - - size_t space = 0; - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer<float> *wb = getWriteRingBuffer(c); - if (wb) { - size_t spaceHere = wb->getWriteSpace(); - if (c == 0 || spaceHere < space) space = spaceHere; - } - } - - if (space == 0) return false; - - size_t f = m_writeBufferFill; - - bool readWriteEqual = (m_readBuffers == m_writeBuffers); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << std::endl; -#endif - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "buffered to " << f << " already" << std::endl; -#endif - - bool resample = (getSourceSampleRate() != getTargetSampleRate()); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << std::endl; -#endif - - size_t channels = getTargetChannelCount(); - - size_t orig = space; - size_t got = 0; - - static float **bufferPtrs = 0; - static size_t bufferPtrCount = 0; - - if (bufferPtrCount < channels) { - if (bufferPtrs) delete[] bufferPtrs; - bufferPtrs = new float *[channels]; - bufferPtrCount = channels; - } - - size_t generatorBlockSize = m_audioGenerator->getBlockSize(); - - if (resample && !m_converter) { - static bool warned = false; - if (!warned) { - std::cerr << "WARNING: sample rates differ, but no converter available!" << std::endl; - warned = true; - } - } - - if (resample && m_converter) { - - double ratio = - double(getTargetSampleRate()) / double(getSourceSampleRate()); - orig = size_t(orig / ratio + 0.1); - - // orig must be a multiple of generatorBlockSize - orig = (orig / generatorBlockSize) * generatorBlockSize; - if (orig == 0) return false; - - size_t work = std::max(orig, space); - - // We only allocate one buffer, but we use it in two halves. - // We place the non-interleaved values in the second half of - // the buffer (orig samples for channel 0, orig samples for - // channel 1 etc), and then interleave them into the first - // half of the buffer. Then we resample back into the second - // half (interleaved) and de-interleave the results back to - // the start of the buffer for insertion into the ringbuffers. - // What a faff -- especially as we've already de-interleaved - // the audio data from the source file elsewhere before we - // even reach this point. - - if (tmpSize < channels * work * 2) { - delete[] tmp; - tmp = new float[channels * work * 2]; - tmpSize = channels * work * 2; - } - - float *nonintlv = tmp + channels * work; - float *intlv = tmp; - float *srcout = tmp + channels * work; - - for (size_t c = 0; c < channels; ++c) { - for (size_t i = 0; i < orig; ++i) { - nonintlv[channels * i + c] = 0.0f; - } - } - - for (size_t c = 0; c < channels; ++c) { - bufferPtrs[c] = nonintlv + c * orig; - } - - got = mixModels(f, orig, bufferPtrs); - - // and interleave into first half - for (size_t c = 0; c < channels; ++c) { - for (size_t i = 0; i < got; ++i) { - float sample = nonintlv[c * got + i]; - intlv[channels * i + c] = sample; - } - } - - SRC_DATA data; - data.data_in = intlv; - data.data_out = srcout; - data.input_frames = got; - data.output_frames = work; - data.src_ratio = ratio; - data.end_of_input = 0; - - int err = src_process(m_converter, &data); -// size_t toCopy = size_t(work * ratio + 0.1); - size_t toCopy = size_t(got * ratio + 0.1); - - if (err) { - std::cerr - << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: " - << src_strerror(err) << std::endl; - //!!! Then what? - } else { - got = data.input_frames_used; - toCopy = data.output_frames_gen; -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "Resampled " << got << " frames to " << toCopy << " frames" << std::endl; -#endif - } - - for (size_t c = 0; c < channels; ++c) { - for (size_t i = 0; i < toCopy; ++i) { - tmp[i] = srcout[channels * i + c]; - } - RingBuffer<float> *wb = getWriteRingBuffer(c); - if (wb) wb->write(tmp, toCopy); - } - - m_writeBufferFill = f; - if (readWriteEqual) m_readBufferFill = f; - - } else { - - // space must be a multiple of generatorBlockSize - space = (space / generatorBlockSize) * generatorBlockSize; - if (space == 0) return false; - - if (tmpSize < channels * space) { - delete[] tmp; - tmp = new float[channels * space]; - tmpSize = channels * space; - } - - for (size_t c = 0; c < channels; ++c) { - - bufferPtrs[c] = tmp + c * space; - - for (size_t i = 0; i < space; ++i) { - tmp[c * space + i] = 0.0f; - } - } - - size_t got = mixModels(f, space, bufferPtrs); - - for (size_t c = 0; c < channels; ++c) { - - RingBuffer<float> *wb = getWriteRingBuffer(c); - if (wb) wb->write(bufferPtrs[c], got); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - if (wb) - std::cerr << "Wrote " << got << " frames for ch " << c << ", now " - << wb->getReadSpace() << " to read" - << std::endl; -#endif - } - - m_writeBufferFill = f; - if (readWriteEqual) m_readBufferFill = f; - - //!!! how do we know when ended? need to mark up a fully-buffered flag and check this if we find the buffers empty in getSourceSamples - } - - return true; -} - -size_t -AudioCallbackPlaySource::mixModels(size_t &frame, size_t count, float **buffers) -{ - size_t processed = 0; - size_t chunkStart = frame; - size_t chunkSize = count; - size_t selectionSize = 0; - size_t nextChunkStart = chunkStart + chunkSize; - - bool looping = m_viewManager->getPlayLoopMode(); - bool constrained = (m_viewManager->getPlaySelectionMode() && - !m_viewManager->getSelections().empty()); - - static float **chunkBufferPtrs = 0; - static size_t chunkBufferPtrCount = 0; - size_t channels = getTargetChannelCount(); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << std::endl; -#endif - - if (chunkBufferPtrCount < channels) { - if (chunkBufferPtrs) delete[] chunkBufferPtrs; - chunkBufferPtrs = new float *[channels]; - chunkBufferPtrCount = channels; - } - - for (size_t c = 0; c < channels; ++c) { - chunkBufferPtrs[c] = buffers[c]; - } - - while (processed < count) { - - chunkSize = count - processed; - nextChunkStart = chunkStart + chunkSize; - selectionSize = 0; - - size_t fadeIn = 0, fadeOut = 0; - - if (constrained) { - - Selection selection = - m_viewManager->getContainingSelection(chunkStart, true); - - if (selection.isEmpty()) { - if (looping) { - selection = *m_viewManager->getSelections().begin(); - chunkStart = selection.getStartFrame(); - fadeIn = 50; - } - } - - if (selection.isEmpty()) { - - chunkSize = 0; - nextChunkStart = chunkStart; - - } else { - - selectionSize = - selection.getEndFrame() - - selection.getStartFrame(); - - if (chunkStart < selection.getStartFrame()) { - chunkStart = selection.getStartFrame(); - fadeIn = 50; - } - - nextChunkStart = chunkStart + chunkSize; - - if (nextChunkStart >= selection.getEndFrame()) { - nextChunkStart = selection.getEndFrame(); - fadeOut = 50; - } - - chunkSize = nextChunkStart - chunkStart; - } - - } else if (looping && m_lastModelEndFrame > 0) { - - if (chunkStart >= m_lastModelEndFrame) { - chunkStart = 0; - } - if (chunkSize > m_lastModelEndFrame - chunkStart) { - chunkSize = m_lastModelEndFrame - chunkStart; - } - nextChunkStart = chunkStart + chunkSize; - } - -// std::cerr << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << std::endl; - - if (!chunkSize) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "Ending selection playback at " << nextChunkStart << std::endl; -#endif - // We need to maintain full buffers so that the other - // thread can tell where it's got to in the playback -- so - // return the full amount here - frame = frame + count; - return count; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << std::endl; -#endif - - size_t got = 0; - - if (selectionSize < 100) { - fadeIn = 0; - fadeOut = 0; - } else if (selectionSize < 300) { - if (fadeIn > 0) fadeIn = 10; - if (fadeOut > 0) fadeOut = 10; - } - - if (fadeIn > 0) { - if (processed * 2 < fadeIn) { - fadeIn = processed * 2; - } - } - - if (fadeOut > 0) { - if ((count - processed - chunkSize) * 2 < fadeOut) { - fadeOut = (count - processed - chunkSize) * 2; - } - } - - for (std::set<Model *>::iterator mi = m_models.begin(); - mi != m_models.end(); ++mi) { - - got = m_audioGenerator->mixModel(*mi, chunkStart, - chunkSize, chunkBufferPtrs, - fadeIn, fadeOut); - } - - for (size_t c = 0; c < channels; ++c) { - chunkBufferPtrs[c] += chunkSize; - } - - processed += chunkSize; - chunkStart = nextChunkStart; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "Returning selection playback " << processed << " frames to " << nextChunkStart << std::endl; -#endif - - frame = nextChunkStart; - return processed; -} - -void -AudioCallbackPlaySource::unifyRingBuffers() -{ - if (m_readBuffers == m_writeBuffers) return; - - // only unify if there will be something to read - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer<float> *wb = getWriteRingBuffer(c); - if (wb) { - if (wb->getReadSpace() < m_blockSize * 2) { - if ((m_writeBufferFill + m_blockSize * 2) < - m_lastModelEndFrame) { - // OK, we don't have enough and there's more to - // read -- don't unify until we can do better - return; - } - } - break; - } - } - - size_t rf = m_readBufferFill; - RingBuffer<float> *rb = getReadRingBuffer(0); - if (rb) { - size_t rs = rb->getReadSpace(); - //!!! incorrect when in non-contiguous selection, see comments elsewhere -// std::cerr << "rs = " << rs << std::endl; - if (rs < rf) rf -= rs; - else rf = 0; - } - - //std::cerr << "m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << std::endl; - - size_t wf = m_writeBufferFill; - size_t skip = 0; - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer<float> *wb = getWriteRingBuffer(c); - if (wb) { - if (c == 0) { - - size_t wrs = wb->getReadSpace(); -// std::cerr << "wrs = " << wrs << std::endl; - - if (wrs < wf) wf -= wrs; - else wf = 0; -// std::cerr << "wf = " << wf << std::endl; - - if (wf < rf) skip = rf - wf; - if (skip == 0) break; - } - -// std::cerr << "skipping " << skip << std::endl; - wb->skip(skip); - } - } - - m_bufferScavenger.claim(m_readBuffers); - m_readBuffers = m_writeBuffers; - m_readBufferFill = m_writeBufferFill; -// std::cerr << "unified" << std::endl; -} - -void -AudioCallbackPlaySource::AudioCallbackPlaySourceFillThread::run() -{ - AudioCallbackPlaySource &s(m_source); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "AudioCallbackPlaySourceFillThread starting" << std::endl; -#endif - - s.m_mutex.lock(); - - bool previouslyPlaying = s.m_playing; - bool work = false; - - while (!s.m_exiting) { - - s.unifyRingBuffers(); - s.m_bufferScavenger.scavenge(); - s.m_timeStretcherScavenger.scavenge(); - - if (work && s.m_playing && s.getSourceSampleRate()) { - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySourceFillThread: not waiting" << std::endl; -#endif - - s.m_mutex.unlock(); - s.m_mutex.lock(); - - } else { - - float ms = 100; - if (s.getSourceSampleRate() > 0) { - ms = float(m_ringBufferSize) / float(s.getSourceSampleRate()) * 1000.0; - } - - if (s.m_playing) ms /= 10; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms << "ms..." << std::endl; -#endif - - s.m_condition.wait(&s.m_mutex, size_t(ms)); - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySourceFillThread: awoken" << std::endl; -#endif - - work = false; - - if (!s.getSourceSampleRate()) continue; - - bool playing = s.m_playing; - - if (playing && !previouslyPlaying) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << std::endl; -#endif - for (size_t c = 0; c < s.getTargetChannelCount(); ++c) { - RingBuffer<float> *rb = s.getReadRingBuffer(c); - if (rb) rb->reset(); - } - } - previouslyPlaying = playing; - - work = s.fillBuffers(); - } - - s.m_mutex.unlock(); -} - - - -#ifdef INCLUDE_MOCFILES -#include "AudioCallbackPlaySource.moc.cpp" -#endif -
--- a/audioio/AudioCallbackPlaySource.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,306 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_CALLBACK_PLAY_SOURCE_H_ -#define _AUDIO_CALLBACK_PLAY_SOURCE_H_ - -#include "base/RingBuffer.h" -#include "base/AudioPlaySource.h" -#include "base/Scavenger.h" - -#include <QObject> -#include <QMutex> -#include <QWaitCondition> - -#include "base/Thread.h" - -#include <samplerate.h> - -#include <set> -#include <map> - -class Model; -class ViewManager; -class AudioGenerator; -class PlayParameters; -class IntegerTimeStretcher; - -/** - * AudioCallbackPlaySource manages audio data supply to callback-based - * audio APIs such as JACK or CoreAudio. It maintains one ring buffer - * per channel, filled during playback by a non-realtime thread, and - * provides a method for a realtime thread to pick up the latest - * available sample data from these buffers. - */ -class AudioCallbackPlaySource : public virtual QObject, - public AudioPlaySource -{ - Q_OBJECT - -public: - AudioCallbackPlaySource(ViewManager *); - virtual ~AudioCallbackPlaySource(); - - /** - * Add a data model to be played from. The source can mix - * playback from a number of sources including dense and sparse - * models. The models must match in sample rate, but they don't - * have to have identical numbers of channels. - */ - virtual void addModel(Model *model); - - /** - * Remove a model. - */ - virtual void removeModel(Model *model); - - /** - * Remove all models. (Silence will ensue.) - */ - virtual void clearModels(); - - /** - * Start making data available in the ring buffers for playback, - * from the given frame. If playback is already under way, reseek - * to the given frame and continue. - */ - virtual void play(size_t startFrame); - - /** - * Stop playback and ensure that no more data is returned. - */ - virtual void stop(); - - /** - * Return whether playback is currently supposed to be happening. - */ - virtual bool isPlaying() const { return m_playing; } - - /** - * Return the frame number that is currently expected to be coming - * out of the speakers. (i.e. compensating for playback latency.) - */ - virtual size_t getCurrentPlayingFrame(); - - /** - * Set the block size of the target audio device. This should - * be called by the target class. - */ - void setTargetBlockSize(size_t); - - /** - * Get the block size of the target audio device. - */ - size_t getTargetBlockSize() const; - - /** - * Set the playback latency of the target audio device, in frames - * at the target sample rate. This is the difference between the - * frame currently "leaving the speakers" and the last frame (or - * highest last frame across all channels) requested via - * getSamples(). The default is zero. - */ - void setTargetPlayLatency(size_t); - - /** - * Get the playback latency of the target audio device. - */ - size_t getTargetPlayLatency() const; - - /** - * Specify that the target audio device has a fixed sample rate - * (i.e. cannot accommodate arbitrary sample rates based on the - * source). If the target sets this to something other than the - * source sample rate, this class will resample automatically to - * fit. - */ - void setTargetSampleRate(size_t); - - /** - * Return the sample rate set by the target audio device (or the - * source sample rate if the target hasn't set one). - */ - virtual size_t getTargetSampleRate() const; - - /** - * Set the current output levels for metering (for call from the - * target) - */ - void setOutputLevels(float left, float right); - - /** - * Return the current (or thereabouts) output levels in the range - * 0.0 -> 1.0, for metering purposes. - */ - virtual bool getOutputLevels(float &left, float &right); - - /** - * Get the number of channels of audio that in the source models. - * This may safely be called from a realtime thread. Returns 0 if - * there is no source yet available. - */ - size_t getSourceChannelCount() const; - - /** - * Get the number of channels of audio that will be provided - * to the play target. This may be more than the source channel - * count: for example, a mono source will provide 2 channels - * after pan. - * This may safely be called from a realtime thread. Returns 0 if - * there is no source yet available. - */ - size_t getTargetChannelCount() const; - - /** - * Get the actual sample rate of the source material. This may - * safely be called from a realtime thread. Returns 0 if there is - * no source yet available. - */ - size_t getSourceSampleRate() const; - - /** - * Get "count" samples (at the target sample rate) of the mixed - * audio data, in all channels. This may safely be called from a - * realtime thread. - */ - size_t getSourceSamples(size_t count, float **buffer); - - void setSlowdownFactor(size_t factor); - -signals: - void modelReplaced(); - - void playStatusChanged(bool isPlaying); - - void sampleRateMismatch(size_t requested, size_t available, bool willResample); - -protected slots: - void selectionChanged(); - void playLoopModeChanged(); - void playSelectionModeChanged(); - void playParametersChanged(PlayParameters *); - -protected: - ViewManager *m_viewManager; - AudioGenerator *m_audioGenerator; - - class RingBufferVector : public std::vector<RingBuffer<float> *> { - public: - virtual ~RingBufferVector() { - while (!empty()) { - delete *begin(); - erase(begin()); - } - } - }; - - std::set<Model *> m_models; - RingBufferVector *m_readBuffers; - RingBufferVector *m_writeBuffers; - size_t m_readBufferFill; - size_t m_writeBufferFill; - Scavenger<RingBufferVector> m_bufferScavenger; - size_t m_sourceChannelCount; - size_t m_blockSize; - size_t m_sourceSampleRate; - size_t m_targetSampleRate; - size_t m_playLatency; - bool m_playing; - bool m_exiting; - size_t m_lastModelEndFrame; - static const size_t m_ringBufferSize; - float m_outputLeft; - float m_outputRight; - - RingBuffer<float> *getWriteRingBuffer(size_t c) { - if (m_writeBuffers && c < m_writeBuffers->size()) { - return (*m_writeBuffers)[c]; - } else { - return 0; - } - } - - RingBuffer<float> *getReadRingBuffer(size_t c) { - RingBufferVector *rb = m_readBuffers; - if (rb && c < rb->size()) { - return (*rb)[c]; - } else { - return 0; - } - } - - void clearRingBuffers(bool haveLock = false, size_t count = 0); - void unifyRingBuffers(); - - class TimeStretcherData - { - public: - TimeStretcherData(size_t channels, size_t factor, size_t blockSize); - ~TimeStretcherData(); - - size_t getFactor() const { return m_factor; } - IntegerTimeStretcher *getStretcher(size_t channel); - float *getOutputBuffer(size_t channel); - float *getInputBuffer(); - - void run(size_t channel); - - protected: - TimeStretcherData(const TimeStretcherData &); // not provided - TimeStretcherData &operator=(const TimeStretcherData &); // not provided - - typedef std::pair<IntegerTimeStretcher *, float *> StretcherBuffer; - std::map<size_t, StretcherBuffer> m_stretcher; - float *m_stretchInputBuffer; - size_t m_factor; - size_t m_blockSize; - }; - - size_t m_slowdownCounter; - TimeStretcherData *m_timeStretcher; - Scavenger<TimeStretcherData> m_timeStretcherScavenger; - - // Called from fill thread, m_playing true, mutex held - // Return true if work done - bool fillBuffers(); - - // Called from fillBuffers. Return the number of frames written, - // which will be count or fewer. Return in the frame argument the - // new buffered frame position (which may be earlier than the - // frame argument passed in, in the case of looping). - size_t mixModels(size_t &frame, size_t count, float **buffers); - - class AudioCallbackPlaySourceFillThread : public Thread - { - public: - AudioCallbackPlaySourceFillThread(AudioCallbackPlaySource &source) : - Thread(Thread::NonRTThread), - m_source(source) { } - - virtual void run(); - - protected: - AudioCallbackPlaySource &m_source; - }; - - QMutex m_mutex; - QWaitCondition m_condition; - AudioCallbackPlaySourceFillThread *m_fillThread; - SRC_STATE *m_converter; -}; - -#endif - -
--- a/audioio/AudioCallbackPlayTarget.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 "AudioCallbackPlayTarget.h" -#include "AudioCallbackPlaySource.h" - -#include <iostream> - -AudioCallbackPlayTarget::AudioCallbackPlayTarget(AudioCallbackPlaySource *source) : - m_source(source), - m_outputGain(1.0) -{ - if (m_source) { - connect(m_source, SIGNAL(modelReplaced()), - this, SLOT(sourceModelReplaced())); - } -} - -AudioCallbackPlayTarget::~AudioCallbackPlayTarget() -{ -} - -void -AudioCallbackPlayTarget::setOutputGain(float gain) -{ - m_outputGain = gain; -} - -#ifdef INCLUDE_MOCFILES -#ifdef INCLUDE_MOCFILES -#include "AudioCallbackPlayTarget.moc.cpp" -#endif -#endif -
--- a/audioio/AudioCallbackPlayTarget.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_CALLBACK_PLAY_TARGET_H_ -#define _AUDIO_CALLBACK_PLAY_TARGET_H_ - -#include <QObject> - -class AudioCallbackPlaySource; - -class AudioCallbackPlayTarget : public QObject -{ - Q_OBJECT - -public: - AudioCallbackPlayTarget(AudioCallbackPlaySource *source); - virtual ~AudioCallbackPlayTarget(); - - virtual bool isOK() const = 0; - - float getOutputGain() const { - return m_outputGain; - } - -public slots: - /** - * Set the playback gain (0.0 = silence, 1.0 = levels unmodified) - */ - virtual void setOutputGain(float gain); - - /** - * The main source model (providing the playback sample rate) has - * been changed. The target should query the source's sample - * rate, set its output sample rate accordingly, and call back on - * the source's setTargetSampleRate to indicate what sample rate - * it succeeded in setting at the output. If this differs from - * the model rate, the source will resample. - */ - virtual void sourceModelReplaced() = 0; - -protected: - AudioCallbackPlaySource *m_source; - float m_outputGain; -}; - -#endif -
--- a/audioio/AudioCoreAudioTarget.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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. -*/ - -#ifdef HAVE_COREAUDIO - -#include "AudioCoreAudioTarget.h" - - - -#endif
--- a/audioio/AudioCoreAudioTarget.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_CORE_AUDIO_TARGET_H_ -#define _AUDIO_CORE_AUDIO_TARGET_H_ - -#ifdef HAVE_COREAUDIO - -#include <jack/jack.h> -#include <vector> - -#include <CoreAudio/CoreAudio.h> -#include <CoreAudio/CoreAudioTypes.h> -#include <AudioUnit/AUComponent.h> -#include <AudioUnit/AudioUnitProperties.h> -#include <AudioUnit/AudioUnitParameters.h> -#include <AudioUnit/AudioOutputUnit.h> - -#include "AudioCallbackPlayTarget.h" - -class AudioCallbackPlaySource; - -class AudioCoreAudioTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioCoreAudioTarget(AudioCallbackPlaySource *source); - ~AudioCoreAudioTarget(); - - virtual bool isOK() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - OSStatus process(void *data, - AudioUnitRenderActionFlags *flags, - const AudioTimeStamp *timestamp, - unsigned int inbus, - unsigned int inframes, - AudioBufferList *ioData); - - int m_bufferSize; - int m_sampleRate; - int m_latency; -}; - -#endif /* HAVE_COREAUDIO */ - -#endif -
--- a/audioio/AudioGenerator.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,764 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 "AudioGenerator.h" - -#include "base/TempDirectory.h" -#include "base/PlayParameters.h" -#include "base/PlayParameterRepository.h" -#include "base/Pitch.h" -#include "base/Exceptions.h" - -#include "model/NoteModel.h" -#include "model/DenseTimeValueModel.h" -#include "model/SparseOneDimensionalModel.h" - -#include "plugin/RealTimePluginFactory.h" -#include "plugin/RealTimePluginInstance.h" -#include "plugin/PluginIdentifier.h" -#include "plugin/PluginXml.h" -#include "plugin/api/alsa/seq_event.h" - -#include <iostream> -#include <math.h> - -#include <QDir> -#include <QFile> - -const size_t -AudioGenerator::m_pluginBlockSize = 2048; - -QString -AudioGenerator::m_sampleDir = ""; - -//#define DEBUG_AUDIO_GENERATOR 1 - -AudioGenerator::AudioGenerator() : - m_sourceSampleRate(0), - m_targetChannelCount(1) -{ - connect(PlayParameterRepository::getInstance(), - SIGNAL(playPluginIdChanged(const Model *, QString)), - this, - SLOT(playPluginIdChanged(const Model *, QString))); - - connect(PlayParameterRepository::getInstance(), - SIGNAL(playPluginConfigurationChanged(const Model *, QString)), - this, - SLOT(playPluginConfigurationChanged(const Model *, QString))); -} - -AudioGenerator::~AudioGenerator() -{ -} - -bool -AudioGenerator::canPlay(const Model *model) -{ - if (dynamic_cast<const DenseTimeValueModel *>(model) || - dynamic_cast<const SparseOneDimensionalModel *>(model) || - dynamic_cast<const NoteModel *>(model)) { - return true; - } else { - return false; - } -} - -bool -AudioGenerator::addModel(Model *model) -{ - if (m_sourceSampleRate == 0) { - - m_sourceSampleRate = model->getSampleRate(); - - } else { - - DenseTimeValueModel *dtvm = - dynamic_cast<DenseTimeValueModel *>(model); - - if (dtvm) { - m_sourceSampleRate = model->getSampleRate(); - return true; - } - } - - RealTimePluginInstance *plugin = loadPluginFor(model); - if (plugin) { - QMutexLocker locker(&m_mutex); - m_synthMap[model] = plugin; - return true; - } - - return false; -} - -void -AudioGenerator::playPluginIdChanged(const Model *model, QString) -{ - if (m_synthMap.find(model) == m_synthMap.end()) return; - - RealTimePluginInstance *plugin = loadPluginFor(model); - if (plugin) { - QMutexLocker locker(&m_mutex); - delete m_synthMap[model]; - m_synthMap[model] = plugin; - } -} - -void -AudioGenerator::playPluginConfigurationChanged(const Model *model, - QString configurationXml) -{ -// std::cerr << "AudioGenerator::playPluginConfigurationChanged" << std::endl; - - if (m_synthMap.find(model) == m_synthMap.end()) { - std::cerr << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << std::endl; - return; - } - - RealTimePluginInstance *plugin = m_synthMap[model]; - if (plugin) { - PluginXml(plugin).setParametersFromXml(configurationXml); - } -} - -QString -AudioGenerator::getDefaultPlayPluginId(const Model *model) -{ - const SparseOneDimensionalModel *sodm = - dynamic_cast<const SparseOneDimensionalModel *>(model); - if (sodm) { - return QString("dssi:%1:sample_player"). - arg(PluginIdentifier::BUILTIN_PLUGIN_SONAME); - } - - const NoteModel *nm = dynamic_cast<const NoteModel *>(model); - if (nm) { - return QString("dssi:%1:sample_player"). - arg(PluginIdentifier::BUILTIN_PLUGIN_SONAME); - } - - return ""; -} - -QString -AudioGenerator::getDefaultPlayPluginConfiguration(const Model *model) -{ - QString program = ""; - - const SparseOneDimensionalModel *sodm = - dynamic_cast<const SparseOneDimensionalModel *>(model); - if (sodm) { - program = "tap"; - } - - const NoteModel *nm = dynamic_cast<const NoteModel *>(model); - if (nm) { - program = "piano"; - } - - if (program == "") return ""; - - return - QString("<plugin configuration=\"%1\" program=\"%2\"/>") - .arg(XmlExportable::encodeEntities - (QString("sampledir=%1") - .arg(PluginXml::encodeConfigurationChars(getSampleDir())))) - .arg(XmlExportable::encodeEntities(program)); -} - -QString -AudioGenerator::getSampleDir() -{ - if (m_sampleDir != "") return m_sampleDir; - - try { - m_sampleDir = TempDirectory::getInstance()->getSubDirectoryPath("samples"); - } catch (DirectoryCreationFailed f) { - std::cerr << "WARNING: AudioGenerator::getSampleDir: Failed to create " - << "temporary sample directory" << std::endl; - m_sampleDir = ""; - return ""; - } - - QDir sampleResourceDir(":/samples", "*.wav"); - - for (unsigned int i = 0; i < sampleResourceDir.count(); ++i) { - - QString fileName(sampleResourceDir[i]); - QFile file(sampleResourceDir.filePath(fileName)); - - if (!file.copy(QDir(m_sampleDir).filePath(fileName))) { - std::cerr << "WARNING: AudioGenerator::getSampleDir: " - << "Unable to copy " << fileName.toStdString() - << " into temporary directory \"" - << m_sampleDir.toStdString() << "\"" << std::endl; - } - } - - return m_sampleDir; -} - -void -AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) -{ - plugin->configure("sampledir", getSampleDir().toStdString()); -} - -RealTimePluginInstance * -AudioGenerator::loadPluginFor(const Model *model) -{ - QString pluginId, configurationXml; - - PlayParameters *parameters = - PlayParameterRepository::getInstance()->getPlayParameters(model); - if (parameters) { - pluginId = parameters->getPlayPluginId(); - configurationXml = parameters->getPlayPluginConfiguration(); - } - - if (pluginId == "") { - pluginId = getDefaultPlayPluginId(model); - configurationXml = getDefaultPlayPluginConfiguration(model); - } - - if (pluginId == "") return 0; - - RealTimePluginInstance *plugin = loadPlugin(pluginId, ""); - if (!plugin) return 0; - - if (configurationXml != "") { - PluginXml(plugin).setParametersFromXml(configurationXml); - } - - if (parameters) { - parameters->setPlayPluginId(pluginId); - parameters->setPlayPluginConfiguration(configurationXml); - } - - return plugin; -} - -RealTimePluginInstance * -AudioGenerator::loadPlugin(QString pluginId, QString program) -{ - RealTimePluginFactory *factory = - RealTimePluginFactory::instanceFor(pluginId); - - if (!factory) { - std::cerr << "Failed to get plugin factory" << std::endl; - return false; - } - - RealTimePluginInstance *instance = - factory->instantiatePlugin - (pluginId, 0, 0, m_sourceSampleRate, m_pluginBlockSize, m_targetChannelCount); - - if (!instance) { - std::cerr << "Failed to instantiate plugin " << pluginId.toStdString() << std::endl; - return 0; - } - - setSampleDir(instance); - - for (unsigned int i = 0; i < instance->getParameterCount(); ++i) { - instance->setParameterValue(i, instance->getParameterDefault(i)); - } - std::string defaultProgram = instance->getProgram(0, 0); - if (defaultProgram != "") { -// std::cerr << "first selecting default program " << defaultProgram << std::endl; - instance->selectProgram(defaultProgram); - } - if (program != "") { -// std::cerr << "now selecting desired program " << program.toStdString() << std::endl; - instance->selectProgram(program.toStdString()); - } - instance->setIdealChannelCount(m_targetChannelCount); // reset! - - return instance; -} - -void -AudioGenerator::removeModel(Model *model) -{ - SparseOneDimensionalModel *sodm = - dynamic_cast<SparseOneDimensionalModel *>(model); - if (!sodm) return; // nothing to do - - QMutexLocker locker(&m_mutex); - - if (m_synthMap.find(sodm) == m_synthMap.end()) return; - - RealTimePluginInstance *instance = m_synthMap[sodm]; - m_synthMap.erase(sodm); - delete instance; -} - -void -AudioGenerator::clearModels() -{ - QMutexLocker locker(&m_mutex); - while (!m_synthMap.empty()) { - RealTimePluginInstance *instance = m_synthMap.begin()->second; - m_synthMap.erase(m_synthMap.begin()); - delete instance; - } -} - -void -AudioGenerator::reset() -{ - QMutexLocker locker(&m_mutex); - for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { - if (i->second) { - i->second->silence(); - i->second->discardEvents(); - } - } - - m_noteOffs.clear(); -} - -void -AudioGenerator::setTargetChannelCount(size_t targetChannelCount) -{ - if (m_targetChannelCount == targetChannelCount) return; - -// std::cerr << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << std::endl; - - QMutexLocker locker(&m_mutex); - m_targetChannelCount = targetChannelCount; - - for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { - if (i->second) i->second->setIdealChannelCount(targetChannelCount); - } -} - -size_t -AudioGenerator::getBlockSize() const -{ - return m_pluginBlockSize; -} - -size_t -AudioGenerator::mixModel(Model *model, size_t startFrame, size_t frameCount, - float **buffer, size_t fadeIn, size_t fadeOut) -{ - if (m_sourceSampleRate == 0) { - std::cerr << "WARNING: AudioGenerator::mixModel: No base source sample rate available" << std::endl; - return frameCount; - } - - QMutexLocker locker(&m_mutex); - - PlayParameters *parameters = - PlayParameterRepository::getInstance()->getPlayParameters(model); - if (!parameters) return frameCount; - - bool playing = !parameters->isPlayMuted(); - if (!playing) return frameCount; - - float gain = parameters->getPlayGain(); - float pan = parameters->getPlayPan(); - - DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model); - if (dtvm) { - return mixDenseTimeValueModel(dtvm, startFrame, frameCount, - buffer, gain, pan, fadeIn, fadeOut); - } - - SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *> - (model); - if (sodm) { - return mixSparseOneDimensionalModel(sodm, startFrame, frameCount, - buffer, gain, pan, fadeIn, fadeOut); - } - - NoteModel *nm = dynamic_cast<NoteModel *>(model); - if (nm) { - return mixNoteModel(nm, startFrame, frameCount, - buffer, gain, pan, fadeIn, fadeOut); - } - - return frameCount; -} - -size_t -AudioGenerator::mixDenseTimeValueModel(DenseTimeValueModel *dtvm, - size_t startFrame, size_t frames, - float **buffer, float gain, float pan, - size_t fadeIn, size_t fadeOut) -{ - static float *channelBuffer = 0; - static size_t channelBufSiz = 0; - - size_t totalFrames = frames + fadeIn/2 + fadeOut/2; - - if (channelBufSiz < totalFrames) { - delete[] channelBuffer; - channelBuffer = new float[totalFrames]; - channelBufSiz = totalFrames; - } - - size_t got = 0; - size_t prevChannel = 999; - - for (size_t c = 0; c < m_targetChannelCount; ++c) { - - size_t sourceChannel = (c % dtvm->getChannelCount()); - -// std::cerr << "mixing channel " << c << " from source channel " << sourceChannel << std::endl; - - float channelGain = gain; - if (pan != 0.0) { - if (c == 0) { - if (pan > 0.0) channelGain *= 1.0 - pan; - } else { - if (pan < 0.0) channelGain *= pan + 1.0; - } - } - - if (prevChannel != sourceChannel) { - if (startFrame >= fadeIn/2) { - got = dtvm->getValues - (sourceChannel, - startFrame - fadeIn/2, startFrame + frames + fadeOut/2, - channelBuffer); - } else { - size_t missing = fadeIn/2 - startFrame; - got = dtvm->getValues - (sourceChannel, - 0, startFrame + frames + fadeOut/2, - channelBuffer + missing); - } - } - prevChannel = sourceChannel; - - for (size_t i = 0; i < fadeIn/2; ++i) { - float *back = buffer[c]; - back -= fadeIn/2; - back[i] += (channelGain * channelBuffer[i] * i) / fadeIn; - } - - for (size_t i = 0; i < frames + fadeOut/2; ++i) { - float mult = channelGain; - if (i < fadeIn/2) { - mult = (mult * i) / fadeIn; - } - if (i > frames - fadeOut/2) { - mult = (mult * ((frames + fadeOut/2) - i)) / fadeOut; - } - buffer[c][i] += mult * channelBuffer[i]; - } - } - - return got; -} - -size_t -AudioGenerator::mixSparseOneDimensionalModel(SparseOneDimensionalModel *sodm, - size_t startFrame, size_t frames, - float **buffer, float gain, float pan, - size_t /* fadeIn */, - size_t /* fadeOut */) -{ - RealTimePluginInstance *plugin = m_synthMap[sodm]; - if (!plugin) return 0; - - size_t latency = plugin->getLatency(); - size_t blocks = frames / m_pluginBlockSize; - - //!!! 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 - //also depends on the JACK block size). how should we ensure that - //all models write the same amount in to the mix, and that we - //always have a multiple of the plugin buffer size? I guess this - //class has to be queryable for the plugin buffer size & the - //callback play source has to use that as a multiple for all the - //calls to mixModel - - size_t got = blocks * m_pluginBlockSize; - -#ifdef DEBUG_AUDIO_GENERATOR - std::cout << "mixModel [sparse]: frames " << frames - << ", blocks " << blocks << std::endl; -#endif - - snd_seq_event_t onEv; - onEv.type = SND_SEQ_EVENT_NOTEON; - onEv.data.note.channel = 0; - onEv.data.note.note = 64; - onEv.data.note.velocity = 127; - - snd_seq_event_t offEv; - offEv.type = SND_SEQ_EVENT_NOTEOFF; - offEv.data.note.channel = 0; - offEv.data.note.velocity = 0; - - NoteOffSet ¬eOffs = m_noteOffs[sodm]; - - for (size_t i = 0; i < blocks; ++i) { - - size_t reqStart = startFrame + i * m_pluginBlockSize; - - SparseOneDimensionalModel::PointList points = - sodm->getPoints(reqStart + latency, - reqStart + latency + m_pluginBlockSize); - - Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime - (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); - - for (SparseOneDimensionalModel::PointList::iterator pli = - points.begin(); pli != points.end(); ++pli) { - - size_t pliFrame = pli->frame; - - if (pliFrame >= latency) pliFrame -= latency; - - if (pliFrame < reqStart || - pliFrame >= reqStart + m_pluginBlockSize) continue; - - while (noteOffs.begin() != noteOffs.end() && - noteOffs.begin()->frame <= pliFrame) { - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (noteOffs.begin()->frame, m_sourceSampleRate); - - offEv.data.note.note = noteOffs.begin()->pitch; - -#ifdef DEBUG_AUDIO_GENERATOR - std::cerr << "mixModel [sparse]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; -#endif - - plugin->sendEvent(eventTime, &offEv); - noteOffs.erase(noteOffs.begin()); - } - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (pliFrame, m_sourceSampleRate); - - plugin->sendEvent(eventTime, &onEv); - -#ifdef DEBUG_AUDIO_GENERATOR - std::cout << "mixModel [sparse]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl; -#endif - - size_t duration = 7000; // frames [for now] - NoteOff noff; - noff.pitch = onEv.data.note.note; - noff.frame = pliFrame + duration; - noteOffs.insert(noff); - } - - while (noteOffs.begin() != noteOffs.end() && - noteOffs.begin()->frame <= - startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (noteOffs.begin()->frame, m_sourceSampleRate); - - offEv.data.note.note = noteOffs.begin()->pitch; - -#ifdef DEBUG_AUDIO_GENERATOR - std::cerr << "mixModel [sparse]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; -#endif - - plugin->sendEvent(eventTime, &offEv); - noteOffs.erase(noteOffs.begin()); - } - - plugin->run(blockTime); - float **outs = plugin->getAudioOutputBuffers(); - - for (size_t c = 0; c < m_targetChannelCount; ++c) { -#ifdef DEBUG_AUDIO_GENERATOR - std::cout << "mixModel [sparse]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl; -#endif - - size_t sourceChannel = (c % plugin->getAudioOutputCount()); - - float channelGain = gain; - if (pan != 0.0) { - if (c == 0) { - if (pan > 0.0) channelGain *= 1.0 - pan; - } else { - if (pan < 0.0) channelGain *= pan + 1.0; - } - } - - for (size_t j = 0; j < m_pluginBlockSize; ++j) { - buffer[c][i * m_pluginBlockSize + j] += - channelGain * outs[sourceChannel][j]; - } - } - } - - return got; -} - - -//!!! mucho duplication with above -- refactor -size_t -AudioGenerator::mixNoteModel(NoteModel *nm, - size_t startFrame, size_t frames, - float **buffer, float gain, float pan, - size_t /* fadeIn */, - size_t /* fadeOut */) -{ - RealTimePluginInstance *plugin = m_synthMap[nm]; - if (!plugin) return 0; - - size_t latency = plugin->getLatency(); - size_t blocks = frames / m_pluginBlockSize; - - //!!! 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 - //also depends on the JACK block size). how should we ensure that - //all models write the same amount in to the mix, and that we - //always have a multiple of the plugin buffer size? I guess this - //class has to be queryable for the plugin buffer size & the - //callback play source has to use that as a multiple for all the - //calls to mixModel - - size_t got = blocks * m_pluginBlockSize; - -#ifdef DEBUG_AUDIO_GENERATOR - std::cout << "mixModel [note]: frames " << frames - << ", blocks " << blocks << std::endl; -#endif - - snd_seq_event_t onEv; - onEv.type = SND_SEQ_EVENT_NOTEON; - onEv.data.note.channel = 0; - onEv.data.note.note = 64; - onEv.data.note.velocity = 127; - - snd_seq_event_t offEv; - offEv.type = SND_SEQ_EVENT_NOTEOFF; - offEv.data.note.channel = 0; - offEv.data.note.velocity = 0; - - NoteOffSet ¬eOffs = m_noteOffs[nm]; - - for (size_t i = 0; i < blocks; ++i) { - - size_t reqStart = startFrame + i * m_pluginBlockSize; - - NoteModel::PointList points = - nm->getPoints(reqStart + latency, - reqStart + latency + m_pluginBlockSize); - - Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime - (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); - - for (NoteModel::PointList::iterator pli = - points.begin(); pli != points.end(); ++pli) { - - size_t pliFrame = pli->frame; - - if (pliFrame >= latency) pliFrame -= latency; - - if (pliFrame < reqStart || - pliFrame >= reqStart + m_pluginBlockSize) continue; - - while (noteOffs.begin() != noteOffs.end() && - noteOffs.begin()->frame <= pliFrame) { - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (noteOffs.begin()->frame, m_sourceSampleRate); - - offEv.data.note.note = noteOffs.begin()->pitch; - -#ifdef DEBUG_AUDIO_GENERATOR - std::cerr << "mixModel [note]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; -#endif - - plugin->sendEvent(eventTime, &offEv); - noteOffs.erase(noteOffs.begin()); - } - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (pliFrame, m_sourceSampleRate); - - if (nm->getScaleUnits() == "Hz") { - onEv.data.note.note = Pitch::getPitchForFrequency(pli->value); - } else { - onEv.data.note.note = lrintf(pli->value); - } - - plugin->sendEvent(eventTime, &onEv); - -#ifdef DEBUG_AUDIO_GENERATOR - std::cout << "mixModel [note]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl; -#endif - - size_t duration = pli->duration; - if (duration == 0 || duration == 1) { - duration = m_sourceSampleRate / 20; - } - NoteOff noff; - noff.pitch = onEv.data.note.note; - noff.frame = pliFrame + duration; - noteOffs.insert(noff); - } - - while (noteOffs.begin() != noteOffs.end() && - noteOffs.begin()->frame <= - startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (noteOffs.begin()->frame, m_sourceSampleRate); - - offEv.data.note.note = noteOffs.begin()->pitch; - -#ifdef DEBUG_AUDIO_GENERATOR - std::cerr << "mixModel [note]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; -#endif - - plugin->sendEvent(eventTime, &offEv); - noteOffs.erase(noteOffs.begin()); - } - - plugin->run(blockTime); - float **outs = plugin->getAudioOutputBuffers(); - - for (size_t c = 0; c < m_targetChannelCount; ++c) { -#ifdef DEBUG_AUDIO_GENERATOR - std::cout << "mixModel [note]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl; -#endif - - size_t sourceChannel = (c % plugin->getAudioOutputCount()); - - float channelGain = gain; - if (pan != 0.0) { - if (c == 0) { - if (pan > 0.0) channelGain *= 1.0 - pan; - } else { - if (pan < 0.0) channelGain *= pan + 1.0; - } - } - - for (size_t j = 0; j < m_pluginBlockSize; ++j) { - buffer[c][i * m_pluginBlockSize + j] += - channelGain * outs[sourceChannel][j]; - } - } - } - - return got; -} -
--- a/audioio/AudioGenerator.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_GENERATOR_H_ -#define _AUDIO_GENERATOR_H_ - -class Model; -class NoteModel; -class DenseTimeValueModel; -class SparseOneDimensionalModel; -class RealTimePluginInstance; - -#include <QObject> -#include <QMutex> - -#include <set> -#include <map> - -class AudioGenerator : public QObject -{ - Q_OBJECT - -public: - AudioGenerator(); - virtual ~AudioGenerator(); - - /** - * Return true if the given model is of a type that we generally - * know how to play. This doesn't guarantee that a specific - * AudioGenerator will actually produce sounds for it (for - * example, it may turn out that a vital plugin is missing). - */ - static bool canPlay(const Model *model); - - static QString getDefaultPlayPluginId(const Model *model); - static QString getDefaultPlayPluginConfiguration(const Model *model); - - /** - * Add a data model to be played from and initialise any necessary - * audio generation code. Returns true if the model will be - * played. (The return value test here is stricter than that for - * canPlay, above.) The model will be added regardless of the - * return value. - */ - virtual bool addModel(Model *model); - - /** - * Remove a model. - */ - virtual void removeModel(Model *model); - - /** - * Remove all models. - */ - virtual void clearModels(); - - /** - * Reset playback, clearing plugins and the like. - */ - virtual void reset(); - - /** - * Set the target channel count. The buffer parameter to mixModel - * must always point to at least this number of arrays. - */ - virtual void setTargetChannelCount(size_t channelCount); - - /** - * Return the internal processing block size. The frameCount - * argument to all mixModel calls must be a multiple of this - * value. - */ - virtual size_t getBlockSize() const; - - /** - * Mix a single model into an output buffer. - */ - virtual size_t mixModel(Model *model, size_t startFrame, size_t frameCount, - float **buffer, size_t fadeIn = 0, size_t fadeOut = 0); - -protected slots: - void playPluginIdChanged(const Model *, QString); - void playPluginConfigurationChanged(const Model *, QString); - -protected: - size_t m_sourceSampleRate; - size_t m_targetChannelCount; - - struct NoteOff { - - int pitch; - size_t frame; - - struct Comparator { - bool operator()(const NoteOff &n1, const NoteOff &n2) const { - return n1.frame < n2.frame; - } - }; - }; - - typedef std::map<const Model *, RealTimePluginInstance *> PluginMap; - - typedef std::set<NoteOff, NoteOff::Comparator> NoteOffSet; - typedef std::map<const Model *, NoteOffSet> NoteOffMap; - - QMutex m_mutex; - PluginMap m_synthMap; - NoteOffMap m_noteOffs; - static QString m_sampleDir; - - virtual RealTimePluginInstance *loadPluginFor(const Model *model); - virtual RealTimePluginInstance *loadPlugin(QString id, QString program); - static QString getSampleDir(); - static void setSampleDir(RealTimePluginInstance *plugin); - - virtual size_t mixDenseTimeValueModel - (DenseTimeValueModel *model, size_t startFrame, size_t frameCount, - float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); - - virtual size_t mixSparseOneDimensionalModel - (SparseOneDimensionalModel *model, size_t startFrame, size_t frameCount, - float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); - - virtual size_t mixNoteModel - (NoteModel *model, size_t startFrame, size_t frameCount, - float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); - - static const size_t m_pluginBlockSize; -}; - -#endif -
--- a/audioio/AudioJACKTarget.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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. -*/ - -#ifdef HAVE_JACK - -#include "AudioJACKTarget.h" -#include "AudioCallbackPlaySource.h" - -#include <iostream> -#include <cmath> - -//#define DEBUG_AUDIO_JACK_TARGET 1 - -AudioJACKTarget::AudioJACKTarget(AudioCallbackPlaySource *source) : - AudioCallbackPlayTarget(source), - m_client(0), - m_bufferSize(0), - m_sampleRate(0) -{ - char name[20]; - strcpy(name, "Sonic Visualiser"); - m_client = jack_client_new(name); - - if (!m_client) { - sprintf(name, "Sonic Visualiser (%d)", (int)getpid()); - m_client = jack_client_new(name); - if (!m_client) { - std::cerr - << "ERROR: AudioJACKTarget: Failed to connect to JACK server" - << std::endl; - } - } - - if (!m_client) return; - - m_bufferSize = jack_get_buffer_size(m_client); - m_sampleRate = jack_get_sample_rate(m_client); - - jack_set_process_callback(m_client, processStatic, this); - - if (jack_activate(m_client)) { - std::cerr << "ERROR: AudioJACKTarget: Failed to activate JACK client" - << std::endl; - } - - if (m_source) { - sourceModelReplaced(); - } -} - -AudioJACKTarget::~AudioJACKTarget() -{ - if (m_client) { - jack_deactivate(m_client); - jack_client_close(m_client); - } -} - -bool -AudioJACKTarget::isOK() const -{ - return (m_client != 0); -} - -int -AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg) -{ - return ((AudioJACKTarget *)arg)->process(nframes); -} - -void -AudioJACKTarget::sourceModelReplaced() -{ - m_mutex.lock(); - - m_source->setTargetBlockSize(m_bufferSize); - m_source->setTargetSampleRate(m_sampleRate); - - size_t channels = m_source->getSourceChannelCount(); - - // Because we offer pan, we always want at least 2 channels - if (channels < 2) channels = 2; - - if (channels == m_outputs.size() || !m_client) { - m_mutex.unlock(); - return; - } - - const char **ports = - jack_get_ports(m_client, NULL, NULL, - JackPortIsPhysical | JackPortIsInput); - size_t physicalPortCount = 0; - while (ports[physicalPortCount]) ++physicalPortCount; - -#ifdef DEBUG_AUDIO_JACK_TARGET - std::cerr << "AudioJACKTarget::sourceModelReplaced: have " << channels << " channels and " << physicalPortCount << " physical ports" << std::endl; -#endif - - while (m_outputs.size() < channels) { - - char name[20]; - jack_port_t *port; - - sprintf(name, "out %d", m_outputs.size() + 1); - - port = jack_port_register(m_client, - name, - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, - 0); - - if (!port) { - std::cerr - << "ERROR: AudioJACKTarget: Failed to create JACK output port " - << m_outputs.size() << std::endl; - } else { - m_source->setTargetPlayLatency(jack_port_get_latency(port)); - } - - if (m_outputs.size() < physicalPortCount) { - jack_connect(m_client, jack_port_name(port), ports[m_outputs.size()]); - } - - m_outputs.push_back(port); - } - - while (m_outputs.size() > channels) { - std::vector<jack_port_t *>::iterator itr = m_outputs.end(); - --itr; - jack_port_t *port = *itr; - if (port) jack_port_unregister(m_client, port); - m_outputs.erase(itr); - } - - m_mutex.unlock(); -} - -int -AudioJACKTarget::process(jack_nframes_t nframes) -{ - if (!m_mutex.tryLock()) { - return 0; - } - - if (m_outputs.empty()) { - m_mutex.unlock(); - return 0; - } - -#ifdef DEBUG_AUDIO_JACK_TARGET - std::cout << "AudioJACKTarget::process(" << nframes << "): have a source" << std::endl; -#endif - -#ifdef DEBUG_AUDIO_JACK_TARGET - if (m_bufferSize != nframes) { - std::cerr << "WARNING: m_bufferSize != nframes (" << m_bufferSize << " != " << nframes << ")" << std::endl; - } -#endif - - float **buffers = (float **)alloca(m_outputs.size() * sizeof(float *)); - - for (size_t ch = 0; ch < m_outputs.size(); ++ch) { - buffers[ch] = (float *)jack_port_get_buffer(m_outputs[ch], nframes); - } - - if (m_source) { - m_source->getSourceSamples(nframes, buffers); - } else { - for (size_t ch = 0; ch < m_outputs.size(); ++ch) { - for (size_t i = 0; i < nframes; ++i) { - buffers[ch][i] = 0.0; - } - } - } - - float peakLeft = 0.0, peakRight = 0.0; - - for (size_t ch = 0; ch < m_outputs.size(); ++ch) { - - float peak = 0.0; - - for (size_t i = 0; i < nframes; ++i) { - buffers[ch][i] *= m_outputGain; - float sample = fabsf(buffers[ch][i]); - if (sample > peak) peak = sample; - } - - if (ch == 0) peakLeft = peak; - if (ch > 0 || m_outputs.size() == 1) peakRight = peak; - } - - if (m_source) { - m_source->setOutputLevels(peakLeft, peakRight); - } - - m_mutex.unlock(); - return 0; -} - - -#ifdef INCLUDE_MOCFILES -#include "AudioJACKTarget.moc.cpp" -#endif - -#endif /* HAVE_JACK */ -
--- a/audioio/AudioJACKTarget.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_JACK_TARGET_H_ -#define _AUDIO_JACK_TARGET_H_ - -#ifdef HAVE_JACK - -#include <jack/jack.h> -#include <vector> - -#include "AudioCallbackPlayTarget.h" - -#include <QMutex> - -class AudioCallbackPlaySource; - -class AudioJACKTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioJACKTarget(AudioCallbackPlaySource *source); - virtual ~AudioJACKTarget(); - - virtual bool isOK() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - int process(jack_nframes_t nframes); - - static int processStatic(jack_nframes_t, void *); - - jack_client_t *m_client; - std::vector<jack_port_t *> m_outputs; - jack_nframes_t m_bufferSize; - jack_nframes_t m_sampleRate; - QMutex m_mutex; -}; - -#endif /* HAVE_JACK */ - -#endif -
--- a/audioio/AudioPortAudioTarget.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,201 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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. -*/ - -#ifdef HAVE_PORTAUDIO - -#include "AudioPortAudioTarget.h" -#include "AudioCallbackPlaySource.h" - -#include <iostream> -#include <cassert> -#include <cmath> - -//#define DEBUG_AUDIO_PORT_AUDIO_TARGET 1 - -AudioPortAudioTarget::AudioPortAudioTarget(AudioCallbackPlaySource *source) : - AudioCallbackPlayTarget(source), - m_stream(0), - m_bufferSize(0), - m_sampleRate(0), - m_latency(0) -{ - PaError err; - - err = Pa_Initialize(); - if (err != paNoError) { - std::cerr << "ERROR: AudioPortAudioTarget: Failed to initialize PortAudio" << std::endl; - return; - } - - m_bufferSize = 1024; - m_sampleRate = 44100; - if (m_source && (m_source->getSourceSampleRate() != 0)) { - m_sampleRate = m_source->getSourceSampleRate(); - } - - m_latency = Pa_GetMinNumBuffers(m_bufferSize, m_sampleRate) * m_bufferSize; - - std::cerr << "\n\n\nLATENCY= " << m_latency << std::endl; - - err = Pa_OpenDefaultStream(&m_stream, 0, 2, paFloat32, - m_sampleRate, m_bufferSize, 0, - processStatic, this); - - if (err != paNoError) { - std::cerr << "ERROR: AudioPortAudioTarget: Failed to open PortAudio stream" << std::endl; - m_stream = 0; - Pa_Terminate(); - return; - } - - err = Pa_StartStream(m_stream); - - if (err != paNoError) { - std::cerr << "ERROR: AudioPortAudioTarget: Failed to start PortAudio stream" << std::endl; - Pa_CloseStream(m_stream); - m_stream = 0; - Pa_Terminate(); - return; - } - - if (m_source) { - std::cerr << "AudioPortAudioTarget: block size " << m_bufferSize << std::endl; - m_source->setTargetBlockSize(m_bufferSize); - m_source->setTargetSampleRate(m_sampleRate); - m_source->setTargetPlayLatency(m_latency); - } -} - -AudioPortAudioTarget::~AudioPortAudioTarget() -{ - if (m_stream) { - PaError err; - err = Pa_CloseStream(m_stream); - if (err != paNoError) { - std::cerr << "ERROR: AudioPortAudioTarget: Failed to close PortAudio stream" << std::endl; - } - Pa_Terminate(); - } -} - -bool -AudioPortAudioTarget::isOK() const -{ - return (m_stream != 0); -} - -int -AudioPortAudioTarget::processStatic(void *input, void *output, - unsigned long nframes, - PaTimestamp outTime, void *data) -{ - return ((AudioPortAudioTarget *)data)->process(input, output, - nframes, outTime); -} - -void -AudioPortAudioTarget::sourceModelReplaced() -{ - m_source->setTargetSampleRate(m_sampleRate); -} - -int -AudioPortAudioTarget::process(void *inputBuffer, void *outputBuffer, - unsigned long nframes, - PaTimestamp) -{ -#ifdef DEBUG_AUDIO_PORT_AUDIO_TARGET - std::cout << "AudioPortAudioTarget::process(" << nframes << ")" << std::endl; -#endif - - if (!m_source) return 0; - - float *output = (float *)outputBuffer; - - assert(nframes <= m_bufferSize); - - static float **tmpbuf = 0; - static size_t tmpbufch = 0; - static size_t tmpbufsz = 0; - - size_t sourceChannels = m_source->getSourceChannelCount(); - - // Because we offer pan, we always want at least 2 channels - if (sourceChannels < 2) sourceChannels = 2; - - if (!tmpbuf || tmpbufch != sourceChannels || tmpbufsz < m_bufferSize) { - - if (tmpbuf) { - for (size_t i = 0; i < tmpbufch; ++i) { - delete[] tmpbuf[i]; - } - delete[] tmpbuf; - } - - tmpbufch = sourceChannels; - tmpbufsz = m_bufferSize; - tmpbuf = new float *[tmpbufch]; - - for (size_t i = 0; i < tmpbufch; ++i) { - tmpbuf[i] = new float[tmpbufsz]; - } - } - - m_source->getSourceSamples(nframes, tmpbuf); - - float peakLeft = 0.0, peakRight = 0.0; - - for (size_t ch = 0; ch < 2; ++ch) { - - float peak = 0.0; - - if (ch < sourceChannels) { - - // PortAudio samples are interleaved - for (size_t i = 0; i < nframes; ++i) { - output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; - float sample = fabsf(output[i * 2 + ch]); - if (sample > peak) peak = sample; - } - - } else if (ch == 1 && sourceChannels == 1) { - - for (size_t i = 0; i < nframes; ++i) { - output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; - float sample = fabsf(output[i * 2 + ch]); - if (sample > peak) peak = sample; - } - - } else { - for (size_t i = 0; i < nframes; ++i) { - output[i * 2 + ch] = 0; - } - } - - if (ch == 0) peakLeft = peak; - if (ch > 0 || sourceChannels == 1) peakRight = peak; - } - - m_source->setOutputLevels(peakLeft, peakRight); - - return 0; -} - -#ifdef INCLUDE_MOCFILES -#include "AudioPortAudioTarget.moc.cpp" -#endif - -#endif /* HAVE_PORTAUDIO */ -
--- a/audioio/AudioPortAudioTarget.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_PORT_AUDIO_TARGET_H_ -#define _AUDIO_PORT_AUDIO_TARGET_H_ - -#ifdef HAVE_PORTAUDIO - -#include <portaudio.h> -#include <vector> - -#include "AudioCallbackPlayTarget.h" - -class AudioCallbackPlaySource; - -class AudioPortAudioTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioPortAudioTarget(AudioCallbackPlaySource *source); - virtual ~AudioPortAudioTarget(); - - virtual bool isOK() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - int process(void *input, void *output, unsigned long frames, - PaTimestamp outTime); - - static int processStatic(void *, void *, unsigned long, - PaTimestamp, void *); - - PortAudioStream *m_stream; - - int m_bufferSize; - int m_sampleRate; - int m_latency; -}; - -#endif /* HAVE_PORTAUDIO */ - -#endif -
--- a/audioio/AudioTargetFactory.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 "AudioTargetFactory.h" - -#include "AudioJACKTarget.h" -#include "AudioCoreAudioTarget.h" -#include "AudioPortAudioTarget.h" - -#include <iostream> - -AudioCallbackPlayTarget * -AudioTargetFactory::createCallbackTarget(AudioCallbackPlaySource *source) -{ - AudioCallbackPlayTarget *target = 0; - -#ifdef HAVE_JACK - target = new AudioJACKTarget(source); - if (target->isOK()) return target; - else { - std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open JACK target" << std::endl; - delete target; - } -#endif - -#ifdef HAVE_COREAUDIO - target = new AudioCoreAudioTarget(source); - if (target->isOK()) return target; - else { - std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open CoreAudio target" << std::endl; - delete target; - } -#endif - -#ifdef HAVE_DIRECTSOUND - target = new AudioDirectSoundTarget(source); - if (target->isOK()) return target; - else { - std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open DirectSound target" << std::endl; - delete target; - } -#endif - -#ifdef HAVE_PORTAUDIO - target = new AudioPortAudioTarget(source); - if (target->isOK()) return target; - else { - std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open PortAudio target" << std::endl; - delete target; - } -#endif - - std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: No suitable targets available" << std::endl; - return 0; -} - -
--- a/audioio/AudioTargetFactory.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _AUDIO_TARGET_FACTORY_H_ -#define _AUDIO_TARGET_FACTORY_H_ - -class AudioCallbackPlaySource; -class AudioCallbackPlayTarget; - -class AudioTargetFactory -{ -public: - static AudioCallbackPlayTarget *createCallbackTarget(AudioCallbackPlaySource *); -}; - -#endif -
--- a/audioio/IntegerTimeStretcher.cpp Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,226 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 "IntegerTimeStretcher.h" - -#include <iostream> -#include <cassert> - -//#define DEBUG_INTEGER_TIME_STRETCHER 1 - -IntegerTimeStretcher::IntegerTimeStretcher(size_t ratio, - size_t maxProcessInputBlockSize, - size_t inputIncrement, - size_t windowSize, - WindowType windowType) : - m_ratio(ratio), - m_n1(inputIncrement), - m_n2(m_n1 * ratio), - m_wlen(std::max(windowSize, m_n2 * 2)), - m_inbuf(m_wlen), - m_outbuf(maxProcessInputBlockSize * ratio) -{ - m_window = new Window<float>(windowType, m_wlen), - - m_time = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * m_wlen); - m_freq = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * m_wlen); - m_dbuf = (float *)fftwf_malloc(sizeof(float) * m_wlen); - - m_plan = fftwf_plan_dft_1d(m_wlen, m_time, m_freq, FFTW_FORWARD, FFTW_ESTIMATE); - m_iplan = fftwf_plan_dft_c2r_1d(m_wlen, m_freq, m_dbuf, FFTW_ESTIMATE); - - m_mashbuf = new float[m_wlen]; - for (int i = 0; i < m_wlen; ++i) { - m_mashbuf[i] = 0.0; - } -} - -IntegerTimeStretcher::~IntegerTimeStretcher() -{ - std::cerr << "IntegerTimeStretcher::~IntegerTimeStretcher" << std::endl; - - fftwf_destroy_plan(m_plan); - fftwf_destroy_plan(m_iplan); - - fftwf_free(m_time); - fftwf_free(m_freq); - fftwf_free(m_dbuf); - - delete m_window; - delete m_mashbuf; -} - -size_t -IntegerTimeStretcher::getProcessingLatency() const -{ - return getWindowSize() - getInputIncrement(); -} - -void -IntegerTimeStretcher::process(float *input, float *output, size_t samples) -{ - // We need to add samples from input to our internal buffer. When - // we have m_windowSize samples in the buffer, we can process it, - // move the samples back by m_n1 and write the output onto our - // internal output buffer. If we have (samples * ratio) samples - // in that, we can write m_n2 of them back to output and return - // (otherwise we have to write zeroes). - - // When we process, we write m_wlen to our fixed output buffer - // (m_mashbuf). We then pull out the first m_n2 samples from that - // buffer, push them into the output ring buffer, and shift - // m_mashbuf left by that amount. - - // The processing latency is then m_wlen - m_n2. - - size_t consumed = 0; - -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "IntegerTimeStretcher::process(" << samples << ", consumed = " << consumed << "), writable " << m_inbuf.getWriteSpace() <<", readable "<< m_outbuf.getReadSpace() << std::endl; -#endif - - while (consumed < samples) { - - size_t writable = m_inbuf.getWriteSpace(); - writable = std::min(writable, samples - consumed); - - if (writable == 0) { - //!!! then what? I don't think this should happen, but - std::cerr << "WARNING: IntegerTimeStretcher::process: writable == 0" << std::endl; - break; - } - -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "writing " << writable << " from index " << consumed << " to inbuf, consumed will be " << consumed + writable << std::endl; -#endif - m_inbuf.write(input + consumed, writable); - consumed += writable; - - while (m_inbuf.getReadSpace() >= m_wlen && - m_outbuf.getWriteSpace() >= m_n2) { - - // We know we have at least m_wlen samples available - // in m_inbuf. We need to peek m_wlen of them for - // processing, and then read m_n1 to advance the read - // pointer. - - size_t got = m_inbuf.peek(m_dbuf, m_wlen); - assert(got == m_wlen); - - processBlock(m_dbuf, m_mashbuf); - -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "writing first " << m_n2 << " from mashbuf, skipping " << m_n1 << " on inbuf " << std::endl; -#endif - m_inbuf.skip(m_n1); - m_outbuf.write(m_mashbuf, m_n2); - - for (size_t i = 0; i < m_wlen - m_n2; ++i) { - m_mashbuf[i] = m_mashbuf[i + m_n2]; - } - for (size_t i = m_wlen - m_n2; i < m_wlen; ++i) { - m_mashbuf[i] = 0.0f; - } - } - -// std::cerr << "WARNING: IntegerTimeStretcher::process: writespace not enough for output increment (" << m_outbuf.getWriteSpace() << " < " << m_n2 << ")" << std::endl; -// } - -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "loop ended: inbuf read space " << m_inbuf.getReadSpace() << ", outbuf write space " << m_outbuf.getWriteSpace() << std::endl; -#endif - } - - if (m_outbuf.getReadSpace() < samples * m_ratio) { - std::cerr << "WARNING: IntegerTimeStretcher::process: not enough data (yet?) (" << m_outbuf.getReadSpace() << " < " << (samples * m_ratio) << ")" << std::endl; - size_t fill = samples * m_ratio - m_outbuf.getReadSpace(); - for (size_t i = 0; i < fill; ++i) { - output[i] = 0.0; - } - m_outbuf.read(output + fill, m_outbuf.getReadSpace()); - } else { -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "enough data - writing " << samples * m_ratio << " from outbuf" << std::endl; -#endif - m_outbuf.read(output, samples * m_ratio); - } - -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "IntegerTimeStretcher::process returning" << std::endl; -#endif -} - -void -IntegerTimeStretcher::processBlock(float *buf, float *out) -{ - size_t i; - - // buf contains m_wlen samples; out contains enough space for - // m_wlen * ratio samples (we mix into out, rather than replacing) - -#ifdef DEBUG_INTEGER_TIME_STRETCHER - std::cerr << "IntegerTimeStretcher::processBlock" << std::endl; -#endif - - m_window->cut(buf); - - for (i = 0; i < m_wlen/2; ++i) { - float temp = buf[i]; - buf[i] = buf[i + m_wlen/2]; - buf[i + m_wlen/2] = temp; - } - - for (i = 0; i < m_wlen; ++i) { - m_time[i][0] = buf[i]; - m_time[i][1] = 0.0; - } - - fftwf_execute(m_plan); // m_time -> m_freq - - for (i = 0; i < m_wlen; ++i) { - - float mag = sqrtf(m_freq[i][0] * m_freq[i][0] + - m_freq[i][1] * m_freq[i][1]); - - float phase = atan2f(m_freq[i][1], m_freq[i][0]); - - phase = phase * m_ratio; - - float real = mag * cosf(phase); - float imag = mag * sinf(phase); - m_freq[i][0] = real; - m_freq[i][1] = imag; - } - - fftwf_execute(m_iplan); // m_freq -> in, inverse fft - - for (i = 0; i < m_wlen/2; ++i) { - float temp = buf[i] / m_wlen; - buf[i] = buf[i + m_wlen/2] / m_wlen; - buf[i + m_wlen/2] = temp; - } - - m_window->cut(buf); - - int div = m_wlen / m_n2; - if (div > 1) div /= 2; - for (i = 0; i < m_wlen; ++i) { - buf[i] /= div; - } - - for (i = 0; i < m_wlen; ++i) { - out[i] += buf[i]; - } -}
--- a/audioio/IntegerTimeStretcher.h Thu Jul 27 16:06:32 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -/* -*- 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 file copyright 2006 Chris Cannam. - - 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 _INTEGER_TIME_STRETCHER_H_ -#define _INTEGER_TIME_STRETCHER_H_ - -#include "base/Window.h" -#include "base/RingBuffer.h" - -#include <fftw3.h> - -/** - * A time stretcher that slows down audio by an integer multiple of - * its original duration, preserving pitch. This uses the simple - * phase vocoder technique from DAFX pp275-276, adding a block-based - * stream oriented API. - * - * Causes significant transient smearing, but sounds good for steady - * notes and is generally predictable. - */ - -class IntegerTimeStretcher -{ -public: - IntegerTimeStretcher(size_t ratio, - size_t maxProcessInputBlockSize, - size_t inputIncrement = 64, - size_t windowSize = 2048, - WindowType windowType = HanningWindow); - virtual ~IntegerTimeStretcher(); - - void process(float *input, float *output, size_t samples); - - /** - * Get the hop size for input. Smaller values may produce better - * results, at a cost in processing time. Larger values are - * faster but increase the likelihood of echo-like effects. The - * default is 64, which is usually pretty good, though heavy on - * processor power. - */ - size_t getInputIncrement() const { return m_n1; } - - /** - * Get the window size for FFT processing. Must be larger than - * the input and output increments. The default is 2048. - */ - size_t getWindowSize() const { return m_wlen; } - - /** - * Get the window type. The default is a Hanning window. - */ - WindowType getWindowType() const { return m_window->getType(); } - - size_t getRatio() const { return m_ratio; } - size_t getOutputIncrement() const { return getInputIncrement() * getRatio(); } - size_t getProcessingLatency() const; - -protected: - void processBlock(float *in, float *out); - - size_t m_ratio; - size_t m_n1; - size_t m_n2; - size_t m_wlen; - Window<float> *m_window; - - fftwf_complex *m_time; - fftwf_complex *m_freq; - float *m_dbuf; - - fftwf_plan m_plan; - fftwf_plan m_iplan; - - RingBuffer<float> m_inbuf; - RingBuffer<float> m_outbuf; - float *m_mashbuf; -}; - -#endif