# HG changeset patch # User Chris Cannam # Date 1461150388 -3600 # Node ID a2a8fa0eed083fe105f1542c8c91fc504d785c2c # Parent f7ec9e410108fe08580cf61e328b73fe8c7e4e1d# Parent 428ce32a8dd9978e3dd6a471c82438e278cb1d40 Merge branches 3.0-integration and imaf_enc to 3.0-plus-imaf diff -r 428ce32a8dd9 -r a2a8fa0eed08 acinclude.m4 --- a/acinclude.m4 Tue Jul 14 15:04:45 2015 +0100 +++ b/acinclude.m4 Wed Apr 20 12:06:28 2016 +0100 @@ -69,6 +69,9 @@ AC_CHECK_PROG(QMAKE, qmake-qt5, $QTDIR/bin/qmake-qt5,,$QTDIR/bin/) fi if test x$QMAKE = x ; then + AC_CHECK_PROG(QMAKE, qt5-qmake, $QTDIR/bin/qt5-qmake,,$QTDIR/bin/) +fi +if test x$QMAKE = x ; then AC_CHECK_PROG(QMAKE, qmake, $QTDIR/bin/qmake,,$QTDIR/bin/) fi if test x$QMAKE = x ; then @@ -78,6 +81,9 @@ AC_CHECK_PROG(QMAKE, qmake-qt5, qmake-qt5,,$PATH) fi if test x$QMAKE = x ; then + AC_CHECK_PROG(QMAKE, qt5-qmake, qt5-qmake,,$PATH) +fi +if test x$QMAKE = x ; then AC_CHECK_PROG(QMAKE, qmake, qmake,,$PATH) fi if test x$QMAKE = x ; then @@ -112,3 +118,146 @@ ]) +# From autoconf archive: + +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++11 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX11 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014 Alexey Sokolov +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; +]]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], + [$2], [optional], [ax_cxx_compile_cxx11_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=c++11 -std=c++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + AC_MSG_NOTICE([No compiler with C++11 support was found]) + else + HAVE_CXX11=1 + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++11 syntax]) + fi + + AC_SUBST(HAVE_CXX11) + fi +]) + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/AudioCallbackPlaySource.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/AudioCallbackPlaySource.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,1883 @@ +/* -*- 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 and QMUL. + + 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 "data/model/Model.h" +#include "base/ViewManagerBase.h" +#include "base/PlayParameterRepository.h" +#include "base/Preferences.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/WaveFileModel.h" +#include "data/model/ReadOnlyWaveFileModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "plugin/RealTimePluginInstance.h" + +#include "bqaudioio/SystemPlaybackTarget.h" + +#include +using namespace RubberBand; + +#include +#include + +//#define DEBUG_AUDIO_PLAY_SOURCE 1 +//#define DEBUG_AUDIO_PLAY_SOURCE_PLAYING 1 + +static const int DEFAULT_RING_BUFFER_SIZE = 131071; + +AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManagerBase *manager, + QString clientName) : + m_viewManager(manager), + m_audioGenerator(new AudioGenerator()), + m_clientName(clientName.toUtf8().data()), + 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_target(0), + m_lastRetrievalTimestamp(0.0), + m_lastRetrievedBlockSize(0), + m_trustworthyTimestamps(true), + m_lastCurrentFrame(0), + m_playing(false), + m_exiting(false), + m_lastModelEndFrame(0), + m_ringBufferSize(DEFAULT_RING_BUFFER_SIZE), + m_outputLeft(0.0), + m_outputRight(0.0), + m_auditioningPlugin(0), + m_auditioningPluginBypassed(false), + m_playStartFrame(0), + m_playStartFramePassed(false), + m_timeStretcher(0), + m_monoStretcher(0), + m_stretchRatio(1.0), + m_stretchMono(false), + m_stretcherInputCount(0), + m_stretcherInputs(0), + m_stretcherInputSizes(0), + m_fillThread(0), + m_converter(0), + m_resampleQuality(Preferences::getInstance()->getResampleQuality()) +{ + 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(this, SIGNAL(playStatusChanged(bool)), + m_viewManager, SLOT(playStatusChanged(bool))); + + connect(PlayParameterRepository::getInstance(), + SIGNAL(playParametersChanged(PlayParameters *)), + this, SLOT(playParametersChanged(PlayParameters *))); + + connect(Preferences::getInstance(), + SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); +} + +AudioCallbackPlaySource::~AudioCallbackPlaySource() +{ +#ifdef DEBUG_AUDIO_PLAY_SOURCE + SVDEBUG << "AudioCallbackPlaySource::~AudioCallbackPlaySource entering" << endl; +#endif + m_exiting = true; + + if (m_fillThread) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource dtor: awakening thread" << endl; +#endif + m_condition.wakeAll(); + m_fillThread->wait(); + delete m_fillThread; + } + + clearModels(); + + if (m_readBuffers != m_writeBuffers) { + delete m_readBuffers; + } + + delete m_writeBuffers; + + delete m_audioGenerator; + + for (int i = 0; i < m_stretcherInputCount; ++i) { + delete[] m_stretcherInputs[i]; + } + delete[] m_stretcherInputSizes; + delete[] m_stretcherInputs; + + delete m_timeStretcher; + delete m_monoStretcher; + + m_bufferScavenger.scavenge(true); + m_pluginScavenger.scavenge(true); +#ifdef DEBUG_AUDIO_PLAY_SOURCE + SVDEBUG << "AudioCallbackPlaySource::~AudioCallbackPlaySource finishing" << endl; +#endif +} + +void +AudioCallbackPlaySource::addModel(Model *model) +{ + if (m_models.find(model) != m_models.end()) return; + + bool willPlay = 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; + + int modelChannels = 1; + ReadOnlyWaveFileModel *rowfm = qobject_cast(model); + if (rowfm) modelChannels = rowfm->getChannelCount(); + if (modelChannels > m_sourceChannelCount) { + m_sourceChannelCount = modelChannels; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource: Adding model with " << modelChannels << " channels at rate " << model->getSampleRate() << endl; +#endif + + if (m_sourceSampleRate == 0) { + + m_sourceSampleRate = model->getSampleRate(); + srChanged = true; + + } else if (model->getSampleRate() != m_sourceSampleRate) { + + // If this is a read-only wave file model and we have no + // other, we can just switch to this model's sample rate + + if (rowfm) { + + bool conflicting = false; + + for (std::set::const_iterator i = m_models.begin(); + i != m_models.end(); ++i) { + // Only read-only wave file models should be + // considered conflicting -- writable wave file models + // are derived and we shouldn't take their rates into + // account. Also, don't give any particular weight to + // a file that's already playing at the wrong rate + // anyway + ReadOnlyWaveFileModel *other = + qobject_cast(*i); + if (other && other != rowfm && + other->getSampleRate() != model->getSampleRate() && + other->getSampleRate() == m_sourceSampleRate) { + SVDEBUG << "AudioCallbackPlaySource::addModel: Conflicting wave file model " << *i << " found" << endl; + conflicting = true; + break; + } + } + + if (conflicting) { + + SVDEBUG << "AudioCallbackPlaySource::addModel: ERROR: " + << "New model sample rate does not match" << endl + << "existing model(s) (new " << model->getSampleRate() + << " vs " << m_sourceSampleRate + << "), playback will be wrong" + << endl; + + emit sampleRateMismatch(model->getSampleRate(), + m_sourceSampleRate, + false); + } else { + m_sourceSampleRate = model->getSampleRate(); + srChanged = true; + } + } + } + + if (!m_writeBuffers || (int)m_writeBuffers->size() < getTargetChannelCount()) { + clearRingBuffers(true, getTargetChannelCount()); + buffersChanged = true; + } else { + if (willPlay) clearRingBuffers(true); + } + + if (buffersChanged || srChanged) { + if (m_converter) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "AudioCallbackPlaySource::addModel: Buffers or sample rate changed, deleting existing SR converter" << endl; +#endif + src_delete(m_converter); + m_converter = 0; + } + } + + rebuildRangeLists(); + + m_mutex.unlock(); + + initialiseConverter(); + + m_audioGenerator->setTargetChannelCount(getTargetChannelCount()); + + if (!m_fillThread) { + m_fillThread = new FillThread(*this); + m_fillThread->start(); + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s) -- emitting modelReplaced" << endl; +#endif + + if (buffersChanged || srChanged) { + emit modelReplaced(); + } + + connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), + this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t))); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::addModel: awakening thread" << endl; +#endif + + m_condition.wakeAll(); +} + +void +AudioCallbackPlaySource::modelChangedWithin(sv_frame_t +#ifdef DEBUG_AUDIO_PLAY_SOURCE + startFrame +#endif + , sv_frame_t endFrame) +{ +#ifdef DEBUG_AUDIO_PLAY_SOURCE + SVDEBUG << "AudioCallbackPlaySource::modelChangedWithin(" << startFrame << "," << endFrame << ")" << endl; +#endif + if (endFrame > m_lastModelEndFrame) { + m_lastModelEndFrame = endFrame; + rebuildRangeLists(); + } +} + +void +AudioCallbackPlaySource::removeModel(Model *model) +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::removeModel(" << model << ")" << endl; +#endif + + disconnect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), + this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t))); + + m_models.erase(model); + + if (m_models.empty()) { + if (m_converter) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "AudioCallbackPlaySource::removeModel: No models left, deleting SR converter" << endl; +#endif + src_delete(m_converter); + m_converter = 0; + } + m_sourceSampleRate = 0; + } + + sv_frame_t lastEnd = 0; + for (std::set::const_iterator i = m_models.begin(); + i != m_models.end(); ++i) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::removeModel(" << model << "): checking end frame on model " << *i << endl; +#endif + if ((*i)->getEndFrame() > lastEnd) { + lastEnd = (*i)->getEndFrame(); + } +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "(done, lastEnd now " << lastEnd << ")" << endl; +#endif + } + m_lastModelEndFrame = lastEnd; + + m_audioGenerator->removeModel(model); + + m_mutex.unlock(); + + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::clearModels() +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::clearModels()" << endl; +#endif + + m_models.clear(); + + if (m_converter) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "AudioCallbackPlaySource::clearModels: Deleting SR converter" << endl; +#endif + src_delete(m_converter); + m_converter = 0; + } + + m_lastModelEndFrame = 0; + + m_sourceSampleRate = 0; + + m_mutex.unlock(); + + m_audioGenerator->clearModels(); + + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::clearRingBuffers(bool haveLock, int count) +{ + if (!haveLock) m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "clearRingBuffers" << endl; +#endif + + rebuildRangeLists(); + + if (count == 0) { + if (m_writeBuffers) count = int(m_writeBuffers->size()); + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "current playing frame = " << getCurrentPlayingFrame() << endl; + + cerr << "write buffer fill (before) = " << m_writeBufferFill << endl; +#endif + + m_writeBufferFill = getCurrentBufferedFrame(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "current buffered frame = " << m_writeBufferFill << endl; +#endif + + if (m_readBuffers != m_writeBuffers) { + delete m_writeBuffers; + } + + m_writeBuffers = new RingBufferVector; + + for (int i = 0; i < count; ++i) { + m_writeBuffers->push_back(new RingBuffer(m_ringBufferSize)); + } + + m_audioGenerator->reset(); + +// cout << "AudioCallbackPlaySource::clearRingBuffers: Created " +// << count << " write buffers" << endl; + + if (!haveLock) { + m_mutex.unlock(); + } +} + +void +AudioCallbackPlaySource::play(sv_frame_t startFrame) +{ + if (!m_sourceSampleRate) { + cerr << "AudioCallbackPlaySource::play: No source sample rate available, not playing" << endl; + return; + } + + if (m_viewManager->getPlaySelectionMode() && + !m_viewManager->getSelections().empty()) { + + SVDEBUG << "AudioCallbackPlaySource::play: constraining frame " << startFrame << " to selection = "; + + startFrame = m_viewManager->constrainFrameToSelection(startFrame); + + SVDEBUG << startFrame << endl; + + } else { + if (startFrame < 0) { + startFrame = 0; + } + if (startFrame >= m_lastModelEndFrame) { + startFrame = 0; + } + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "play(" << startFrame << ") -> playback model "; +#endif + + startFrame = m_viewManager->alignReferenceToPlaybackFrame(startFrame); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << startFrame << endl; +#endif + + // 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. + // NO -- we can end up playing some first -- always reset here + + m_mutex.lock(); + + if (m_timeStretcher) { + m_timeStretcher->reset(); + } + if (m_monoStretcher) { + m_monoStretcher->reset(); + } + + m_readBufferFill = m_writeBufferFill = startFrame; + if (m_readBuffers) { + for (int c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *rb = getReadRingBuffer(c); +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "reset ring buffer for channel " << c << endl; +#endif + if (rb) rb->reset(); + } + } + if (m_converter) src_reset(m_converter); + + m_mutex.unlock(); + + m_audioGenerator->reset(); + + m_playStartFrame = startFrame; + m_playStartFramePassed = false; + m_playStartedAt = RealTime::zeroTime; + if (m_target) { + m_playStartedAt = RealTime::fromSeconds(m_target->getCurrentTime()); + } + + bool changed = !m_playing; + m_lastRetrievalTimestamp = 0; + m_lastCurrentFrame = 0; + m_playing = true; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::play: awakening thread" << endl; +#endif + + m_condition.wakeAll(); + if (changed) { + emit playStatusChanged(m_playing); + emit activity(tr("Play from %1").arg + (RealTime::frame2RealTime + (m_playStartFrame, m_sourceSampleRate).toText().c_str())); + } +} + +void +AudioCallbackPlaySource::stop() +{ +#ifdef DEBUG_AUDIO_PLAY_SOURCE + SVDEBUG << "AudioCallbackPlaySource::stop()" << endl; +#endif + bool changed = m_playing; + m_playing = false; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::stop: awakening thread" << endl; +#endif + + m_condition.wakeAll(); + m_lastRetrievalTimestamp = 0; + if (changed) { + emit playStatusChanged(m_playing); + emit activity(tr("Stop at %1").arg + (RealTime::frame2RealTime + (m_lastCurrentFrame, m_sourceSampleRate).toText().c_str())); + } + m_lastCurrentFrame = 0; +} + +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 *) +{ + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName n) +{ + if (n == "Resample Quality") { + setResampleQuality(Preferences::getInstance()->getResampleQuality()); + } +} + +void +AudioCallbackPlaySource::audioProcessingOverload() +{ + cerr << "Audio processing overload!" << endl; + + if (!m_playing) return; + + RealTimePluginInstance *ap = m_auditioningPlugin; + if (ap && !m_auditioningPluginBypassed) { + m_auditioningPluginBypassed = true; + emit audioOverloadPluginDisabled(); + return; + } + + if (m_timeStretcher && + m_timeStretcher->getTimeRatio() < 1.0 && + m_stretcherInputCount > 1 && + m_monoStretcher && !m_stretchMono) { + m_stretchMono = true; + emit audioTimeStretchMultiChannelDisabled(); + return; + } +} + +void +AudioCallbackPlaySource::setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *target) +{ + m_target = target; +} + +void +AudioCallbackPlaySource::setSystemPlaybackBlockSize(int size) +{ + cout << "AudioCallbackPlaySource::setTarget: Block size -> " << size << endl; + if (size != 0) { + m_blockSize = size; + } + if (size * 4 > m_ringBufferSize) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "AudioCallbackPlaySource::setTarget: Buffer size " + << size << " > a quarter of ring buffer size " + << m_ringBufferSize << ", calling for more ring buffer" + << endl; +#endif + m_ringBufferSize = size * 4; + if (m_writeBuffers && !m_writeBuffers->empty()) { + clearRingBuffers(); + } + } +} + +int +AudioCallbackPlaySource::getTargetBlockSize() const +{ +// cout << "AudioCallbackPlaySource::getTargetBlockSize() -> " << m_blockSize << endl; + return int(m_blockSize); +} + +void +AudioCallbackPlaySource::setSystemPlaybackLatency(int latency) +{ + m_playLatency = latency; +} + +sv_frame_t +AudioCallbackPlaySource::getTargetPlayLatency() const +{ + return m_playLatency; +} + +sv_frame_t +AudioCallbackPlaySource::getCurrentPlayingFrame() +{ + // This method attempts to estimate which audio sample frame is + // "currently coming through the speakers". + + sv_samplerate_t targetRate = getTargetSampleRate(); + sv_frame_t latency = m_playLatency; // at target rate + RealTime latency_t = RealTime::zeroTime; + + if (targetRate != 0) { + latency_t = RealTime::frame2RealTime(latency, targetRate); + } + + return getCurrentFrame(latency_t); +} + +sv_frame_t +AudioCallbackPlaySource::getCurrentBufferedFrame() +{ + return getCurrentFrame(RealTime::zeroTime); +} + +sv_frame_t +AudioCallbackPlaySource::getCurrentFrame(RealTime latency_t) +{ + // We resample when filling the ring buffer, and time-stretch when + // draining it. The buffer contains data at the "target rate" and + // the latency provided by the target is also at the target rate. + // Because of the multiple rates involved, we do the actual + // calculation using RealTime instead. + + sv_samplerate_t sourceRate = getSourceSampleRate(); + sv_samplerate_t targetRate = getTargetSampleRate(); + + if (sourceRate == 0 || targetRate == 0) return 0; + + int inbuffer = 0; // at target rate + + for (int c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + int here = rb->getReadSpace(); + if (c == 0 || here < inbuffer) inbuffer = here; + } + } + + sv_frame_t readBufferFill = m_readBufferFill; + sv_frame_t lastRetrievedBlockSize = m_lastRetrievedBlockSize; + double lastRetrievalTimestamp = m_lastRetrievalTimestamp; + double currentTime = 0.0; + if (m_target) currentTime = m_target->getCurrentTime(); + + bool looping = m_viewManager->getPlayLoopMode(); + + RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, targetRate); + + sv_frame_t stretchlat = 0; + double timeRatio = 1.0; + + if (m_timeStretcher) { + stretchlat = m_timeStretcher->getLatency(); + timeRatio = m_timeStretcher->getTimeRatio(); + } + + RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, targetRate); + + // When the target has just requested a block from us, the last + // sample it obtained was our buffer fill frame count minus the + // amount of read space (converted back to source sample rate) + // remaining now. That sample is not expected to be played until + // the target's play latency has elapsed. By the time the + // following block is requested, that sample will be at the + // target's play latency minus the last requested block size away + // from being played. + + RealTime sincerequest_t = RealTime::zeroTime; + RealTime lastretrieved_t = RealTime::zeroTime; + + if (m_target && + m_trustworthyTimestamps && + lastRetrievalTimestamp != 0.0) { + + lastretrieved_t = RealTime::frame2RealTime + (lastRetrievedBlockSize, targetRate); + + // calculate number of frames at target rate that have elapsed + // since the end of the last call to getSourceSamples + + if (m_trustworthyTimestamps && !looping) { + + // this adjustment seems to cause more problems when looping + double elapsed = currentTime - lastRetrievalTimestamp; + + if (elapsed > 0.0) { + sincerequest_t = RealTime::fromSeconds(elapsed); + } + } + + } else { + + lastretrieved_t = RealTime::frame2RealTime + (getTargetBlockSize(), targetRate); + } + + RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, sourceRate); + + if (timeRatio != 1.0) { + lastretrieved_t = lastretrieved_t / timeRatio; + sincerequest_t = sincerequest_t / timeRatio; + latency_t = latency_t / timeRatio; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cerr << "\nbuffered to: " << bufferedto_t << ", in buffer: " << inbuffer_t << ", time ratio " << timeRatio << "\n stretcher latency: " << stretchlat_t << ", device latency: " << latency_t << "\n since request: " << sincerequest_t << ", last retrieved quantity: " << lastretrieved_t << endl; +#endif + + // Normally the range lists should contain at least one item each + // -- if playback is unconstrained, that item should report the + // entire source audio duration. + + if (m_rangeStarts.empty()) { + rebuildRangeLists(); + } + + if (m_rangeStarts.empty()) { + // this code is only used in case of error in rebuildRangeLists + RealTime playing_t = bufferedto_t + - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t + + sincerequest_t; + if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; + sv_frame_t frame = RealTime::realTime2Frame(playing_t, sourceRate); + return m_viewManager->alignPlaybackFrameToReference(frame); + } + + int inRange = 0; + int index = 0; + + for (int i = 0; i < (int)m_rangeStarts.size(); ++i) { + if (bufferedto_t >= m_rangeStarts[i]) { + inRange = index; + } else { + break; + } + ++index; + } + + if (inRange >= int(m_rangeStarts.size())) { + inRange = int(m_rangeStarts.size())-1; + } + + RealTime playing_t = bufferedto_t; + + playing_t = playing_t + - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t + + sincerequest_t; + + // This rather gross little hack is used to ensure that latency + // compensation doesn't result in the playback pointer appearing + // to start earlier than the actual playback does. It doesn't + // work properly (hence the bail-out in the middle) because if we + // are playing a relatively short looped region, the playing time + // estimated from the buffer fill frame may have wrapped around + // the region boundary and end up being much smaller than the + // theoretical play start frame, perhaps even for the entire + // duration of playback! + + if (!m_playStartFramePassed) { + RealTime playstart_t = RealTime::frame2RealTime(m_playStartFrame, + sourceRate); + if (playing_t < playstart_t) { +// cerr << "playing_t " << playing_t << " < playstart_t " +// << playstart_t << endl; + if (/*!!! sincerequest_t > RealTime::zeroTime && */ + m_playStartedAt + latency_t + stretchlat_t < + RealTime::fromSeconds(currentTime)) { +// cerr << "but we've been playing for long enough that I think we should disregard it (it probably results from loop wrapping)" << endl; + m_playStartFramePassed = true; + } else { + playing_t = playstart_t; + } + } else { + m_playStartFramePassed = true; + } + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cerr << "playing_t " << playing_t; +#endif + + playing_t = playing_t - m_rangeStarts[inRange]; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cerr << " as offset into range " << inRange << " (start =" << m_rangeStarts[inRange] << " duration =" << m_rangeDurations[inRange] << ") = " << playing_t << endl; +#endif + + while (playing_t < RealTime::zeroTime) { + + if (inRange == 0) { + if (looping) { + inRange = int(m_rangeStarts.size()) - 1; + } else { + break; + } + } else { + --inRange; + } + + playing_t = playing_t + m_rangeDurations[inRange]; + } + + playing_t = playing_t + m_rangeStarts[inRange]; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cerr << " playing time: " << playing_t << endl; +#endif + + if (!looping) { + if (inRange == (int)m_rangeStarts.size()-1 && + playing_t >= m_rangeStarts[inRange] + m_rangeDurations[inRange]) { +cerr << "Not looping, inRange " << inRange << " == rangeStarts.size()-1, playing_t " << playing_t << " >= m_rangeStarts[inRange] " << m_rangeStarts[inRange] << " + m_rangeDurations[inRange] " << m_rangeDurations[inRange] << " -- stopping" << endl; + stop(); + } + } + + if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; + + sv_frame_t frame = RealTime::realTime2Frame(playing_t, sourceRate); + + if (m_lastCurrentFrame > 0 && !looping) { + if (frame < m_lastCurrentFrame) { + frame = m_lastCurrentFrame; + } + } + + m_lastCurrentFrame = frame; + + return m_viewManager->alignPlaybackFrameToReference(frame); +} + +void +AudioCallbackPlaySource::rebuildRangeLists() +{ + bool constrained = (m_viewManager->getPlaySelectionMode()); + + m_rangeStarts.clear(); + m_rangeDurations.clear(); + + sv_samplerate_t sourceRate = getSourceSampleRate(); + if (sourceRate == 0) return; + + RealTime end = RealTime::frame2RealTime(m_lastModelEndFrame, sourceRate); + if (end == RealTime::zeroTime) return; + + if (!constrained) { + m_rangeStarts.push_back(RealTime::zeroTime); + m_rangeDurations.push_back(end); + return; + } + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + MultiSelection::SelectionList::const_iterator i; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + SVDEBUG << "AudioCallbackPlaySource::rebuildRangeLists" << endl; +#endif + + if (!selections.empty()) { + + for (i = selections.begin(); i != selections.end(); ++i) { + + RealTime start = + (RealTime::frame2RealTime + (m_viewManager->alignReferenceToPlaybackFrame(i->getStartFrame()), + sourceRate)); + RealTime duration = + (RealTime::frame2RealTime + (m_viewManager->alignReferenceToPlaybackFrame(i->getEndFrame()) - + m_viewManager->alignReferenceToPlaybackFrame(i->getStartFrame()), + sourceRate)); + + m_rangeStarts.push_back(start); + m_rangeDurations.push_back(duration); + } + } else { + m_rangeStarts.push_back(RealTime::zeroTime); + m_rangeDurations.push_back(end); + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "Now have " << m_rangeStarts.size() << " play ranges" << endl; +#endif +} + +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::setSystemPlaybackSampleRate(int sr) +{ + bool first = (m_targetSampleRate == 0); + + m_targetSampleRate = sr; + initialiseConverter(); + + if (first && (m_stretchRatio != 1.f)) { + // couldn't create a stretcher before because we had no sample + // rate: make one now + setTimeStretch(m_stretchRatio); + } +} + +void +AudioCallbackPlaySource::initialiseConverter() +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "AudioCallbackPlaySource::initialiseConverter(): from " + << getSourceSampleRate() << " to " << getTargetSampleRate() << endl; +#endif + + if (m_converter) { + src_delete(m_converter); + m_converter = 0; + } + + if (getSourceSampleRate() != getTargetSampleRate()) { + + int err = 0; + + m_converter = src_new(m_resampleQuality == 2 ? SRC_SINC_BEST_QUALITY : + m_resampleQuality == 1 ? SRC_SINC_MEDIUM_QUALITY : + m_resampleQuality == 0 ? SRC_SINC_FASTEST : + SRC_SINC_MEDIUM_QUALITY, + getTargetChannelCount(), &err); + + if (!m_converter) { + cerr << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: " + << src_strerror(err) << endl; + + m_mutex.unlock(); + + emit sampleRateMismatch(getSourceSampleRate(), + getTargetSampleRate(), + false); + } else { + + m_mutex.unlock(); + + emit sampleRateMismatch(getSourceSampleRate(), + getTargetSampleRate(), + true); + } + } else { + m_mutex.unlock(); + } +} + +void +AudioCallbackPlaySource::setResampleQuality(int q) +{ + if (q == m_resampleQuality) return; + m_resampleQuality = q; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + SVDEBUG << "AudioCallbackPlaySource::setResampleQuality: setting to " + << m_resampleQuality << endl; +#endif + + initialiseConverter(); +} + +void +AudioCallbackPlaySource::setAuditioningEffect(Auditionable *a) +{ + RealTimePluginInstance *plugin = dynamic_cast(a); + if (a && !plugin) { + cerr << "WARNING: AudioCallbackPlaySource::setAuditioningEffect: auditionable object " << a << " is not a real-time plugin instance" << endl; + } + + m_mutex.lock(); + m_auditioningPlugin = plugin; + m_auditioningPluginBypassed = false; + m_mutex.unlock(); +} + +void +AudioCallbackPlaySource::setSoloModelSet(std::set s) +{ + m_audioGenerator->setSoloModelSet(s); + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::clearSoloModelSet() +{ + m_audioGenerator->clearSoloModelSet(); + clearRingBuffers(); +} + +sv_samplerate_t +AudioCallbackPlaySource::getTargetSampleRate() const +{ + if (m_targetSampleRate) return m_targetSampleRate; + else return getSourceSampleRate(); +} + +int +AudioCallbackPlaySource::getSourceChannelCount() const +{ + return m_sourceChannelCount; +} + +int +AudioCallbackPlaySource::getTargetChannelCount() const +{ + if (m_sourceChannelCount < 2) return 2; + return m_sourceChannelCount; +} + +sv_samplerate_t +AudioCallbackPlaySource::getSourceSampleRate() const +{ + return m_sourceSampleRate; +} + +void +AudioCallbackPlaySource::setTimeStretch(double factor) +{ + m_stretchRatio = factor; + + if (!getTargetSampleRate()) return; // have to make our stretcher later + + if (m_timeStretcher || (factor == 1.0)) { + // stretch ratio will be set in next process call if appropriate + } else { + m_stretcherInputCount = getTargetChannelCount(); + RubberBandStretcher *stretcher = new RubberBandStretcher + (int(getTargetSampleRate()), + m_stretcherInputCount, + RubberBandStretcher::OptionProcessRealTime, + factor); + RubberBandStretcher *monoStretcher = new RubberBandStretcher + (int(getTargetSampleRate()), + 1, + RubberBandStretcher::OptionProcessRealTime, + factor); + m_stretcherInputs = new float *[m_stretcherInputCount]; + m_stretcherInputSizes = new sv_frame_t[m_stretcherInputCount]; + for (int c = 0; c < m_stretcherInputCount; ++c) { + m_stretcherInputSizes[c] = 16384; + m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; + } + m_monoStretcher = monoStretcher; + m_timeStretcher = stretcher; + } + + emit activity(tr("Change time-stretch factor to %1").arg(factor)); +} + +int +AudioCallbackPlaySource::getSourceSamples(int count, float **buffer) +{ + if (!m_playing) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Not playing" << endl; +#endif + for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + for (int i = 0; i < count; ++i) { + buffer[ch][i] = 0.0; + } + } + return 0; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Playing" << endl; +#endif + + // Ensure that all buffers have at least the amount of data we + // need -- else reduce the size of our requests correspondingly + + for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + + RingBuffer *rb = getReadRingBuffer(ch); + + if (!rb) { + cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " + << "No ring buffer available for channel " << ch + << ", returning no data here" << endl; + count = 0; + break; + } + + int rs = rb->getReadSpace(); + if (rs < count) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " + << "Ring buffer for channel " << ch << " has only " + << rs << " (of " << count << ") samples available (" + << "ring buffer size is " << rb->getSize() << ", write " + << "space " << rb->getWriteSpace() << "), " + << "reducing request size" << endl; +#endif + count = rs; + } + } + + if (count == 0) return 0; + + RubberBandStretcher *ts = m_timeStretcher; + RubberBandStretcher *ms = m_monoStretcher; + + double ratio = ts ? ts->getTimeRatio() : 1.0; + + if (ratio != m_stretchRatio) { + if (!ts) { + cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: Time ratio change to " << m_stretchRatio << " is pending, but no stretcher is set" << endl; + m_stretchRatio = 1.0; + } else { + ts->setTimeRatio(m_stretchRatio); + if (ms) ms->setTimeRatio(m_stretchRatio); + if (m_stretchRatio >= 1.0) m_stretchMono = false; + } + } + + int stretchChannels = m_stretcherInputCount; + if (m_stretchMono) { + if (ms) { + ts = ms; + stretchChannels = 1; + } else { + m_stretchMono = false; + } + } + + if (m_target) { + m_lastRetrievedBlockSize = count; + m_lastRetrievalTimestamp = m_target->getCurrentTime(); + } + + if (!ts || ratio == 1.f) { + + int got = 0; + + for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + + RingBuffer *rb = getReadRingBuffer(ch); + + if (rb) { + + // this is marginally more likely to leave our channels in + // sync after a processing failure than just passing "count": + sv_frame_t request = count; + if (ch > 0) request = got; + + got = rb->read(buffer[ch], int(request)); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cout << "AudioCallbackPlaySource::getSamples: got " << got << " (of " << count << ") samples on channel " << ch << ", signalling for more (possibly)" << endl; +#endif + } + + for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + for (int i = got; i < count; ++i) { + buffer[ch][i] = 0.0; + } + } + } + + applyAuditioningEffect(count, buffer); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::getSamples: awakening thread" << endl; +#endif + + m_condition.wakeAll(); + + return got; + } + + int channels = getTargetChannelCount(); + sv_frame_t available; + sv_frame_t fedToStretcher = 0; + int warned = 0; + + // The input block for a given output is approx output / ratio, + // but we can't predict it exactly, for an adaptive timestretcher. + + while ((available = ts->available()) < count) { + + sv_frame_t reqd = lrint(double(count - available) / ratio); + reqd = std::max(reqd, sv_frame_t(ts->getSamplesRequired())); + if (reqd == 0) reqd = 1; + + sv_frame_t got = reqd; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cerr << "reqd = " <= m_stretcherInputCount) continue; + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + sv_frame_t gotHere; + if (stretchChannels == 1 && c > 0) { + gotHere = rb->readAdding(m_stretcherInputs[0], int(got)); + } else { + gotHere = rb->read(m_stretcherInputs[c], int(got)); + } + if (gotHere < got) got = gotHere; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + if (c == 0) { + SVDEBUG << "feeding stretcher: got " << gotHere + << ", " << rb->getReadSpace() << " remain" << endl; + } +#endif + + } else { + cerr << "WARNING: No ring buffer available for channel " << c << " in stretcher input block" << endl; + } + } + + if (got < reqd) { + cerr << "WARNING: Read underrun in playback (" + << got << " < " << reqd << ")" << endl; + } + + ts->process(m_stretcherInputs, size_t(got), false); + + fedToStretcher += got; + + if (got == 0) break; + + if (ts->available() == available) { + cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << endl; + if (++warned == 5) break; + } + } + + ts->retrieve(buffer, size_t(count)); + + for (int c = stretchChannels; c < getTargetChannelCount(); ++c) { + for (int i = 0; i < count; ++i) { + buffer[c][i] = buffer[0][i]; + } + } + + applyAuditioningEffect(count, buffer); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::getSamples [stretched]: awakening thread" << endl; +#endif + + m_condition.wakeAll(); + + return count; +} + +void +AudioCallbackPlaySource::applyAuditioningEffect(sv_frame_t count, float **buffers) +{ + if (m_auditioningPluginBypassed) return; + RealTimePluginInstance *plugin = m_auditioningPlugin; + if (!plugin) return; + + if ((int)plugin->getAudioInputCount() != getTargetChannelCount()) { +// cerr << "plugin input count " << plugin->getAudioInputCount() +// << " != our channel count " << getTargetChannelCount() +// << endl; + return; + } + if ((int)plugin->getAudioOutputCount() != getTargetChannelCount()) { +// cerr << "plugin output count " << plugin->getAudioOutputCount() +// << " != our channel count " << getTargetChannelCount() +// << endl; + return; + } + if ((int)plugin->getBufferSize() < count) { +// cerr << "plugin buffer size " << plugin->getBufferSize() +// << " < our block size " << count +// << endl; + return; + } + + float **ib = plugin->getAudioInputBuffers(); + float **ob = plugin->getAudioOutputBuffers(); + + for (int c = 0; c < getTargetChannelCount(); ++c) { + for (int i = 0; i < count; ++i) { + ib[c][i] = buffers[c][i]; + } + } + + plugin->run(Vamp::RealTime::zeroTime, int(count)); + + for (int c = 0; c < getTargetChannelCount(); ++c) { + for (int i = 0; i < count; ++i) { + buffers[c][i] = ob[c][i]; + } + } +} + +// Called from fill thread, m_playing true, mutex held +bool +AudioCallbackPlaySource::fillBuffers() +{ + static float *tmp = 0; + static sv_frame_t tmpSize = 0; + + sv_frame_t space = 0; + for (int c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + sv_frame_t spaceHere = wb->getWriteSpace(); + if (c == 0 || spaceHere < space) space = spaceHere; + } + } + + if (space == 0) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySourceFillThread: no space to fill" << endl; +#endif + return false; + } + + sv_frame_t f = m_writeBufferFill; + + bool readWriteEqual = (m_readBuffers == m_writeBuffers); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + if (!readWriteEqual) { + cout << "AudioCallbackPlaySourceFillThread: note read buffers != write buffers" << endl; + } + cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << endl; +#endif + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "buffered to " << f << " already" << endl; +#endif + + bool resample = (getSourceSampleRate() != getTargetSampleRate()); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << endl; +#endif + + int channels = getTargetChannelCount(); + + sv_frame_t orig = space; + sv_frame_t got = 0; + + static float **bufferPtrs = 0; + static int bufferPtrCount = 0; + + if (bufferPtrCount < channels) { + if (bufferPtrs) delete[] bufferPtrs; + bufferPtrs = new float *[channels]; + bufferPtrCount = channels; + } + + sv_frame_t generatorBlockSize = m_audioGenerator->getBlockSize(); + + if (resample && !m_converter) { + throw std::logic_error("Sample rates differ, but no converter available!"); + } + + if (resample && m_converter) { + + double ratio = + double(getTargetSampleRate()) / double(getSourceSampleRate()); + orig = sv_frame_t(double(orig) / ratio + 0.1); + + // orig must be a multiple of generatorBlockSize + orig = (orig / generatorBlockSize) * generatorBlockSize; + if (orig == 0) return false; + + sv_frame_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 (int c = 0; c < channels; ++c) { + for (int i = 0; i < orig; ++i) { + nonintlv[channels * i + c] = 0.0f; + } + } + + for (int c = 0; c < channels; ++c) { + bufferPtrs[c] = nonintlv + c * orig; + } + + got = mixModels(f, orig, bufferPtrs); // also modifies f + + // and interleave into first half + for (int c = 0; c < channels; ++c) { + for (int 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 = long(got); + data.output_frames = long(work); + data.src_ratio = ratio; + data.end_of_input = 0; + + int err = src_process(m_converter, &data); + + sv_frame_t toCopy = sv_frame_t(double(got) * ratio + 0.1); + + if (err) { + cerr + << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: " + << src_strerror(err) << endl; + //!!! Then what? + } else { + got = data.input_frames_used; + toCopy = data.output_frames_gen; +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Resampled " << got << " frames to " << toCopy << " frames" << endl; +#endif + } + + for (int c = 0; c < channels; ++c) { + for (int i = 0; i < toCopy; ++i) { + tmp[i] = srcout[channels * i + c]; + } + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) wb->write(tmp, int(toCopy)); + } + + m_writeBufferFill = f; + if (readWriteEqual) m_readBufferFill = f; + + } else { + + // space must be a multiple of generatorBlockSize + sv_frame_t reqSpace = space; + space = (reqSpace / generatorBlockSize) * generatorBlockSize; + if (space == 0) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "requested fill of " << reqSpace + << " is less than generator block size of " + << generatorBlockSize << ", leaving it" << endl; +#endif + return false; + } + + if (tmpSize < channels * space) { + delete[] tmp; + tmp = new float[channels * space]; + tmpSize = channels * space; + } + + for (int c = 0; c < channels; ++c) { + + bufferPtrs[c] = tmp + c * space; + + for (int i = 0; i < space; ++i) { + tmp[c * space + i] = 0.0f; + } + } + + sv_frame_t got = mixModels(f, space, bufferPtrs); // also modifies f + + for (int c = 0; c < channels; ++c) { + + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + int actual = wb->write(bufferPtrs[c], int(got)); +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Wrote " << actual << " samples for ch " << c << ", now " + << wb->getReadSpace() << " to read" + << endl; +#endif + if (actual < got) { + cerr << "WARNING: Buffer overrun in channel " << c + << ": wrote " << actual << " of " << got + << " samples" << endl; + } + } + } + + m_writeBufferFill = f; + if (readWriteEqual) m_readBufferFill = f; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Read buffer fill is now " << m_readBufferFill << endl; +#endif + + //!!! 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; +} + +sv_frame_t +AudioCallbackPlaySource::mixModels(sv_frame_t &frame, sv_frame_t count, float **buffers) +{ + sv_frame_t processed = 0; + sv_frame_t chunkStart = frame; + sv_frame_t chunkSize = count; + sv_frame_t selectionSize = 0; + sv_frame_t nextChunkStart = chunkStart + chunkSize; + + bool looping = m_viewManager->getPlayLoopMode(); + bool constrained = (m_viewManager->getPlaySelectionMode() && + !m_viewManager->getSelections().empty()); + + static float **chunkBufferPtrs = 0; + static int chunkBufferPtrCount = 0; + int channels = getTargetChannelCount(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << endl; +#endif + + if (chunkBufferPtrCount < channels) { + if (chunkBufferPtrs) delete[] chunkBufferPtrs; + chunkBufferPtrs = new float *[channels]; + chunkBufferPtrCount = channels; + } + + for (int c = 0; c < channels; ++c) { + chunkBufferPtrs[c] = buffers[c]; + } + + while (processed < count) { + + chunkSize = count - processed; + nextChunkStart = chunkStart + chunkSize; + selectionSize = 0; + + sv_frame_t fadeIn = 0, fadeOut = 0; + + if (constrained) { + + sv_frame_t rChunkStart = + m_viewManager->alignPlaybackFrameToReference(chunkStart); + + Selection selection = + m_viewManager->getContainingSelection(rChunkStart, true); + + if (selection.isEmpty()) { + if (looping) { + selection = *m_viewManager->getSelections().begin(); + chunkStart = m_viewManager->alignReferenceToPlaybackFrame + (selection.getStartFrame()); + fadeIn = 50; + } + } + + if (selection.isEmpty()) { + + chunkSize = 0; + nextChunkStart = chunkStart; + + } else { + + sv_frame_t sf = m_viewManager->alignReferenceToPlaybackFrame + (selection.getStartFrame()); + sv_frame_t ef = m_viewManager->alignReferenceToPlaybackFrame + (selection.getEndFrame()); + + selectionSize = ef - sf; + + if (chunkStart < sf) { + chunkStart = sf; + fadeIn = 50; + } + + nextChunkStart = chunkStart + chunkSize; + + if (nextChunkStart >= ef) { + nextChunkStart = ef; + 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; + } + +// cout << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << endl; + + if (!chunkSize) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Ending selection playback at " << nextChunkStart << 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 + cout << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << endl; +#endif + + 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::iterator mi = m_models.begin(); + mi != m_models.end(); ++mi) { + + (void) m_audioGenerator->mixModel(*mi, chunkStart, + chunkSize, chunkBufferPtrs, + fadeIn, fadeOut); + } + + for (int c = 0; c < channels; ++c) { + chunkBufferPtrs[c] += chunkSize; + } + + processed += chunkSize; + chunkStart = nextChunkStart; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Returning selection playback " << processed << " frames to " << nextChunkStart << 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 (int c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *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 +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + SVDEBUG << "AudioCallbackPlaySource::unifyRingBuffers: Not unifying: write buffer has less (" << wb->getReadSpace() << ") than " << m_blockSize*2 << " to read and write buffer fill (" << m_writeBufferFill << ") is not close to end frame (" << m_lastModelEndFrame << ")" << endl; +#endif + return; + } + } + break; + } + } + + sv_frame_t rf = m_readBufferFill; + RingBuffer *rb = getReadRingBuffer(0); + if (rb) { + int rs = rb->getReadSpace(); + //!!! incorrect when in non-contiguous selection, see comments elsewhere +// cout << "rs = " << rs << endl; + if (rs < rf) rf -= rs; + else rf = 0; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + SVDEBUG << "AudioCallbackPlaySource::unifyRingBuffers: m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << endl; +#endif + + sv_frame_t wf = m_writeBufferFill; + sv_frame_t skip = 0; + for (int c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + if (c == 0) { + + int wrs = wb->getReadSpace(); +// cout << "wrs = " << wrs << endl; + + if (wrs < wf) wf -= wrs; + else wf = 0; +// cout << "wf = " << wf << endl; + + if (wf < rf) skip = rf - wf; + if (skip == 0) break; + } + +// cout << "skipping " << skip << endl; + wb->skip(int(skip)); + } + } + + m_bufferScavenger.claim(m_readBuffers); + m_readBuffers = m_writeBuffers; + m_readBufferFill = m_writeBufferFill; +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cerr << "unified" << endl; +#endif +} + +void +AudioCallbackPlaySource::FillThread::run() +{ + AudioCallbackPlaySource &s(m_source); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySourceFillThread starting" << 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_pluginScavenger.scavenge(); + + if (work && s.m_playing && s.getSourceSampleRate()) { + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySourceFillThread: not waiting" << endl; +#endif + + s.m_mutex.unlock(); + s.m_mutex.lock(); + + } else { + + double ms = 100; + if (s.getSourceSampleRate() > 0) { + ms = double(s.m_ringBufferSize) / s.getSourceSampleRate() * 1000.0; + } + + if (s.m_playing) ms /= 10; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + if (!s.m_playing) cout << endl; + cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms << "ms..." << endl; +#endif + + s.m_condition.wait(&s.m_mutex, int(ms)); + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySourceFillThread: awoken" << endl; +#endif + + work = false; + + if (!s.getSourceSampleRate()) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySourceFillThread: source sample rate is zero" << endl; +#endif + continue; + } + + bool playing = s.m_playing; + + if (playing && !previouslyPlaying) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << endl; +#endif + for (int c = 0; c < s.getTargetChannelCount(); ++c) { + RingBuffer *rb = s.getReadRingBuffer(c); + if (rb) rb->reset(); + } + } + previouslyPlaying = playing; + + work = s.fillBuffers(); + } + + s.m_mutex.unlock(); +} + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/AudioCallbackPlaySource.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/AudioCallbackPlaySource.h Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,405 @@ +/* -*- 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 and QMUL. + + 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/PropertyContainer.h" +#include "base/Scavenger.h" + +#include + +#include +#include +#include + +#include "base/Thread.h" +#include "base/RealTime.h" + +#include + +#include +#include + +namespace RubberBand { + class RubberBandStretcher; +} + +class Model; +class ViewManagerBase; +class AudioGenerator; +class PlayParameters; +class RealTimePluginInstance; +class AudioCallbackPlayTarget; + +/** + * 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 QObject, + public AudioPlaySource, + public breakfastquay::ApplicationPlaybackSource +{ + Q_OBJECT + +public: + AudioCallbackPlaySource(ViewManagerBase *, QString clientName); + 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(sv_frame_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 sv_frame_t getCurrentPlayingFrame(); + + /** + * Return the last frame that would come out of the speakers if we + * stopped playback right now. + */ + virtual sv_frame_t getCurrentBufferedFrame(); + + /** + * Return the frame at which playback is expected to end (if not looping). + */ + virtual sv_frame_t getPlayEndFrame() { return m_lastModelEndFrame; } + + /** + * Set the playback target. + */ + virtual void setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *); + + /** + * Set the block size of the target audio device. This should be + * called by the target class. + */ + virtual void setSystemPlaybackBlockSize(int blockSize); + + /** + * Get the block size of the target audio device. This may be an + * estimate or upper bound, if the target has a variable block + * size; the source should behave itself even if this value turns + * out to be inaccurate. + */ + int 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 setSystemPlaybackLatency(int); + + /** + * Get the playback latency of the target audio device. + */ + sv_frame_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 setSystemPlaybackSampleRate(int); + + /** + * Return the sample rate set by the target audio device (or the + * source sample rate if the target hasn't set one). + */ + virtual sv_samplerate_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. + */ + int 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. + */ + int getTargetChannelCount() const; + + /** + * ApplicationPlaybackSource equivalent of the above. + */ + virtual int getApplicationChannelCount() const { + return getTargetChannelCount(); + } + + /** + * 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. + */ + virtual sv_samplerate_t getSourceSampleRate() const; + + /** + * ApplicationPlaybackSource equivalent of the above. + */ + virtual int getApplicationSampleRate() const { + return int(round(getSourceSampleRate())); + } + + /** + * 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. + */ + virtual int getSourceSamples(int count, float **buffer); + + /** + * Set the time stretcher factor (i.e. playback speed). + */ + void setTimeStretch(double factor); + + /** + * Set the resampler quality, 0 - 2 where 0 is fastest and 2 is + * highest quality. + */ + void setResampleQuality(int q); + + /** + * Set a single real-time plugin as a processing effect for + * auditioning during playback. + * + * The plugin must have been initialised with + * getTargetChannelCount() channels and a getTargetBlockSize() + * sample frame processing block size. + * + * This playback source takes ownership of the plugin, which will + * be deleted at some point after the following call to + * setAuditioningEffect (depending on real-time constraints). + * + * Pass a null pointer to remove the current auditioning plugin, + * if any. + */ + void setAuditioningEffect(Auditionable *plugin); + + /** + * Specify that only the given set of models should be played. + */ + void setSoloModelSet(std::sets); + + /** + * Specify that all models should be played as normal (if not + * muted). + */ + void clearSoloModelSet(); + + std::string getClientName() const { return m_clientName; } + +signals: + void modelReplaced(); + + void playStatusChanged(bool isPlaying); + + void sampleRateMismatch(sv_samplerate_t requested, + sv_samplerate_t available, + bool willResample); + + void audioOverloadPluginDisabled(); + void audioTimeStretchMultiChannelDisabled(); + + void activity(QString); + +public slots: + void audioProcessingOverload(); + +protected slots: + void selectionChanged(); + void playLoopModeChanged(); + void playSelectionModeChanged(); + void playParametersChanged(PlayParameters *); + void preferenceChanged(PropertyContainer::PropertyName); + void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame); + +protected: + ViewManagerBase *m_viewManager; + AudioGenerator *m_audioGenerator; + std::string m_clientName; + + class RingBufferVector : public std::vector *> { + public: + virtual ~RingBufferVector() { + while (!empty()) { + delete *begin(); + erase(begin()); + } + } + }; + + std::set m_models; + RingBufferVector *m_readBuffers; + RingBufferVector *m_writeBuffers; + sv_frame_t m_readBufferFill; + sv_frame_t m_writeBufferFill; + Scavenger m_bufferScavenger; + int m_sourceChannelCount; + sv_frame_t m_blockSize; + sv_samplerate_t m_sourceSampleRate; + sv_samplerate_t m_targetSampleRate; + sv_frame_t m_playLatency; + breakfastquay::SystemPlaybackTarget *m_target; + double m_lastRetrievalTimestamp; + sv_frame_t m_lastRetrievedBlockSize; + bool m_trustworthyTimestamps; + sv_frame_t m_lastCurrentFrame; + bool m_playing; + bool m_exiting; + sv_frame_t m_lastModelEndFrame; + int m_ringBufferSize; + float m_outputLeft; + float m_outputRight; + RealTimePluginInstance *m_auditioningPlugin; + bool m_auditioningPluginBypassed; + Scavenger m_pluginScavenger; + sv_frame_t m_playStartFrame; + bool m_playStartFramePassed; + RealTime m_playStartedAt; + + RingBuffer *getWriteRingBuffer(int c) { + if (m_writeBuffers && c < (int)m_writeBuffers->size()) { + return (*m_writeBuffers)[c]; + } else { + return 0; + } + } + + RingBuffer *getReadRingBuffer(int c) { + RingBufferVector *rb = m_readBuffers; + if (rb && c < (int)rb->size()) { + return (*rb)[c]; + } else { + return 0; + } + } + + void clearRingBuffers(bool haveLock = false, int count = 0); + void unifyRingBuffers(); + + RubberBand::RubberBandStretcher *m_timeStretcher; + RubberBand::RubberBandStretcher *m_monoStretcher; + double m_stretchRatio; + bool m_stretchMono; + + int m_stretcherInputCount; + float **m_stretcherInputs; + sv_frame_t *m_stretcherInputSizes; + + // 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). + sv_frame_t mixModels(sv_frame_t &frame, sv_frame_t count, float **buffers); + + // Called from getSourceSamples. + void applyAuditioningEffect(sv_frame_t count, float **buffers); + + // Ranges of current selections, if play selection is active + std::vector m_rangeStarts; + std::vector m_rangeDurations; + void rebuildRangeLists(); + + sv_frame_t getCurrentFrame(RealTime outputLatency); + + class FillThread : public Thread + { + public: + FillThread(AudioCallbackPlaySource &source) : + Thread(Thread::NonRTThread), + m_source(source) { } + + virtual void run(); + + protected: + AudioCallbackPlaySource &m_source; + }; + + QMutex m_mutex; + QWaitCondition m_condition; + FillThread *m_fillThread; + SRC_STATE *m_converter; + int m_resampleQuality; + void initialiseConverter(); +}; + +#endif + + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/AudioGenerator.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/AudioGenerator.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,710 @@ +/* -*- 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 "data/model/NoteModel.h" +#include "data/model/FlexiNoteModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "data/model/NoteData.h" + +#include "ClipMixer.h" +#include "ContinuousSynth.h" + +#include +#include + +#include +#include + +const sv_frame_t +AudioGenerator::m_processingBlockSize = 1024; + +QString +AudioGenerator::m_sampleDir = ""; + +//#define DEBUG_AUDIO_GENERATOR 1 + +AudioGenerator::AudioGenerator() : + m_sourceSampleRate(0), + m_targetChannelCount(1), + m_waveType(0), + m_soloing(false), + m_channelBuffer(0), + m_channelBufSiz(0), + m_channelBufCount(0) +{ + initialiseSampleDir(); + + connect(PlayParameterRepository::getInstance(), + SIGNAL(playClipIdChanged(const Playable *, QString)), + this, + SLOT(playClipIdChanged(const Playable *, QString))); +} + +AudioGenerator::~AudioGenerator() +{ +#ifdef DEBUG_AUDIO_GENERATOR + SVDEBUG << "AudioGenerator::~AudioGenerator" << endl; +#endif +} + +void +AudioGenerator::initialiseSampleDir() +{ + if (m_sampleDir != "") return; + + try { + m_sampleDir = TempDirectory::getInstance()->getSubDirectoryPath("samples"); + } catch (DirectoryCreationFailed f) { + cerr << "WARNING: AudioGenerator::initialiseSampleDir:" + << " Failed to create temporary sample directory" + << 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)); + QString target = QDir(m_sampleDir).filePath(fileName); + + if (!file.copy(target)) { + cerr << "WARNING: AudioGenerator::getSampleDir: " + << "Unable to copy " << fileName + << " into temporary directory \"" + << m_sampleDir << "\"" << endl; + } else { + QFile tf(target); + tf.setPermissions(tf.permissions() | + QFile::WriteOwner | + QFile::WriteUser); + } + } +} + +bool +AudioGenerator::addModel(Model *model) +{ + if (m_sourceSampleRate == 0) { + + m_sourceSampleRate = model->getSampleRate(); + + } else { + + DenseTimeValueModel *dtvm = + dynamic_cast(model); + + if (dtvm) { + m_sourceSampleRate = model->getSampleRate(); + return true; + } + } + + const Playable *playable = model; + if (!playable || !playable->canPlay()) return 0; + + PlayParameters *parameters = + PlayParameterRepository::getInstance()->getPlayParameters(playable); + + bool willPlay = !parameters->isPlayMuted(); + + if (usesClipMixer(model)) { + ClipMixer *mixer = makeClipMixerFor(model); + if (mixer) { + QMutexLocker locker(&m_mutex); + m_clipMixerMap[model] = mixer; + return willPlay; + } + } + + if (usesContinuousSynth(model)) { + ContinuousSynth *synth = makeSynthFor(model); + if (synth) { + QMutexLocker locker(&m_mutex); + m_continuousSynthMap[model] = synth; + return willPlay; + } + } + + return false; +} + +void +AudioGenerator::playClipIdChanged(const Playable *playable, QString) +{ + const Model *model = dynamic_cast(playable); + if (!model) { + cerr << "WARNING: AudioGenerator::playClipIdChanged: playable " + << playable << " is not a supported model type" + << endl; + return; + } + + if (m_clipMixerMap.find(model) == m_clipMixerMap.end()) return; + + ClipMixer *mixer = makeClipMixerFor(model); + if (mixer) { + QMutexLocker locker(&m_mutex); + m_clipMixerMap[model] = mixer; + } +} + +bool +AudioGenerator::usesClipMixer(const Model *model) +{ + bool clip = + (qobject_cast(model) || + qobject_cast(model) || + qobject_cast(model)); + return clip; +} + +bool +AudioGenerator::wantsQuieterClips(const Model *model) +{ + // basically, anything that usually has sustain (like notes) or + // often has multiple sounds at once (like notes) wants to use a + // quieter level than simple click tracks + bool does = + (qobject_cast(model) || + qobject_cast(model)); + return does; +} + +bool +AudioGenerator::usesContinuousSynth(const Model *model) +{ + bool cont = + (qobject_cast(model)); + return cont; +} + +ClipMixer * +AudioGenerator::makeClipMixerFor(const Model *model) +{ + QString clipId; + + const Playable *playable = model; + if (!playable || !playable->canPlay()) return 0; + + PlayParameters *parameters = + PlayParameterRepository::getInstance()->getPlayParameters(playable); + if (parameters) { + clipId = parameters->getPlayClipId(); + } + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): sample id = " << clipId << std::endl; +#endif + + if (clipId == "") { + SVDEBUG << "AudioGenerator::makeClipMixerFor(" << model << "): no sample, skipping" << endl; + return 0; + } + + ClipMixer *mixer = new ClipMixer(m_targetChannelCount, + m_sourceSampleRate, + m_processingBlockSize); + + double clipF0 = Pitch::getFrequencyForPitch(60, 0, 440.0); // required + + QString clipPath = QString("%1/%2.wav").arg(m_sampleDir).arg(clipId); + + double level = wantsQuieterClips(model) ? 0.5 : 1.0; + if (!mixer->loadClipData(clipPath, clipF0, level)) { + delete mixer; + return 0; + } + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): loaded clip " << clipId << std::endl; +#endif + + return mixer; +} + +ContinuousSynth * +AudioGenerator::makeSynthFor(const Model *model) +{ + const Playable *playable = model; + if (!playable || !playable->canPlay()) return 0; + + ContinuousSynth *synth = new ContinuousSynth(m_targetChannelCount, + m_sourceSampleRate, + m_processingBlockSize, + m_waveType); + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "AudioGenerator::makeSynthFor(" << model << "): created synth" << std::endl; +#endif + + return synth; +} + +void +AudioGenerator::removeModel(Model *model) +{ + SparseOneDimensionalModel *sodm = + dynamic_cast(model); + if (!sodm) return; // nothing to do + + QMutexLocker locker(&m_mutex); + + if (m_clipMixerMap.find(sodm) == m_clipMixerMap.end()) return; + + ClipMixer *mixer = m_clipMixerMap[sodm]; + m_clipMixerMap.erase(sodm); + delete mixer; +} + +void +AudioGenerator::clearModels() +{ + QMutexLocker locker(&m_mutex); + + while (!m_clipMixerMap.empty()) { + ClipMixer *mixer = m_clipMixerMap.begin()->second; + m_clipMixerMap.erase(m_clipMixerMap.begin()); + delete mixer; + } +} + +void +AudioGenerator::reset() +{ + QMutexLocker locker(&m_mutex); + +#ifdef DEBUG_AUDIO_GENERATOR + cerr << "AudioGenerator::reset()" << endl; +#endif + + for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { + if (i->second) { + i->second->reset(); + } + } + + m_noteOffs.clear(); +} + +void +AudioGenerator::setTargetChannelCount(int targetChannelCount) +{ + if (m_targetChannelCount == targetChannelCount) return; + +// SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << endl; + + QMutexLocker locker(&m_mutex); + m_targetChannelCount = targetChannelCount; + + for (ClipMixerMap::iterator i = m_clipMixerMap.begin(); i != m_clipMixerMap.end(); ++i) { + if (i->second) i->second->setChannelCount(targetChannelCount); + } +} + +sv_frame_t +AudioGenerator::getBlockSize() const +{ + return m_processingBlockSize; +} + +void +AudioGenerator::setSoloModelSet(std::set s) +{ + QMutexLocker locker(&m_mutex); + + m_soloModelSet = s; + m_soloing = true; +} + +void +AudioGenerator::clearSoloModelSet() +{ + QMutexLocker locker(&m_mutex); + + m_soloModelSet.clear(); + m_soloing = false; +} + +sv_frame_t +AudioGenerator::mixModel(Model *model, sv_frame_t startFrame, sv_frame_t frameCount, + float **buffer, sv_frame_t fadeIn, sv_frame_t fadeOut) +{ + if (m_sourceSampleRate == 0) { + cerr << "WARNING: AudioGenerator::mixModel: No base source sample rate available" << endl; + return frameCount; + } + + QMutexLocker locker(&m_mutex); + + Playable *playable = model; + if (!playable || !playable->canPlay()) return frameCount; + + PlayParameters *parameters = + PlayParameterRepository::getInstance()->getPlayParameters(playable); + if (!parameters) return frameCount; + + bool playing = !parameters->isPlayMuted(); + if (!playing) { +#ifdef DEBUG_AUDIO_GENERATOR + cout << "AudioGenerator::mixModel(" << model << "): muted" << endl; +#endif + return frameCount; + } + + if (m_soloing) { + if (m_soloModelSet.find(model) == m_soloModelSet.end()) { +#ifdef DEBUG_AUDIO_GENERATOR + cout << "AudioGenerator::mixModel(" << model << "): not one of the solo'd models" << endl; +#endif + return frameCount; + } + } + + float gain = parameters->getPlayGain(); + float pan = parameters->getPlayPan(); + + DenseTimeValueModel *dtvm = dynamic_cast(model); + if (dtvm) { + return mixDenseTimeValueModel(dtvm, startFrame, frameCount, + buffer, gain, pan, fadeIn, fadeOut); + } + + if (usesClipMixer(model)) { + return mixClipModel(model, startFrame, frameCount, + buffer, gain, pan); + } + + if (usesContinuousSynth(model)) { + return mixContinuousSynthModel(model, startFrame, frameCount, + buffer, gain, pan); + } + + std::cerr << "AudioGenerator::mixModel: WARNING: Model " << model << " of type " << model->getTypeName() << " is marked as playable, but I have no mechanism to play it" << std::endl; + + return frameCount; +} + +sv_frame_t +AudioGenerator::mixDenseTimeValueModel(DenseTimeValueModel *dtvm, + sv_frame_t startFrame, sv_frame_t frames, + float **buffer, float gain, float pan, + sv_frame_t fadeIn, sv_frame_t fadeOut) +{ + sv_frame_t maxFrames = frames + std::max(fadeIn, fadeOut); + + int modelChannels = dtvm->getChannelCount(); + + if (m_channelBufSiz < maxFrames || m_channelBufCount < modelChannels) { + + for (int c = 0; c < m_channelBufCount; ++c) { + delete[] m_channelBuffer[c]; + } + + delete[] m_channelBuffer; + m_channelBuffer = new float *[modelChannels]; + + for (int c = 0; c < modelChannels; ++c) { + m_channelBuffer[c] = new float[maxFrames]; + } + + m_channelBufCount = modelChannels; + m_channelBufSiz = maxFrames; + } + + sv_frame_t got = 0; + + if (startFrame >= fadeIn/2) { + + auto data = dtvm->getMultiChannelData(0, modelChannels - 1, + startFrame - fadeIn/2, + frames + fadeOut/2 + fadeIn/2); + + for (int c = 0; c < modelChannels; ++c) { + copy(data[c].begin(), data[c].end(), m_channelBuffer[c]); + } + + got = data[0].size(); + + } else { + sv_frame_t missing = fadeIn/2 - startFrame; + + if (missing > 0) { + cerr << "note: channelBufSiz = " << m_channelBufSiz + << ", frames + fadeOut/2 = " << frames + fadeOut/2 + << ", startFrame = " << startFrame + << ", missing = " << missing << endl; + } + + auto data = dtvm->getMultiChannelData(0, modelChannels - 1, + startFrame, + frames + fadeOut/2); + for (int c = 0; c < modelChannels; ++c) { + copy(data[c].begin(), data[c].end(), m_channelBuffer[c] + missing); + } + + got = data[0].size() + missing; + } + + for (int c = 0; c < m_targetChannelCount; ++c) { + + int sourceChannel = (c % modelChannels); + +// SVDEBUG << "mixing channel " << c << " from source channel " << sourceChannel << endl; + + float channelGain = gain; + if (pan != 0.0) { + if (c == 0) { + if (pan > 0.0) channelGain *= 1.0f - pan; + } else { + if (pan < 0.0) channelGain *= pan + 1.0f; + } + } + + for (sv_frame_t i = 0; i < fadeIn/2; ++i) { + float *back = buffer[c]; + back -= fadeIn/2; + back[i] += + (channelGain * m_channelBuffer[sourceChannel][i] * float(i)) + / float(fadeIn); + } + + for (sv_frame_t i = 0; i < frames + fadeOut/2; ++i) { + float mult = channelGain; + if (i < fadeIn/2) { + mult = (mult * float(i)) / float(fadeIn); + } + if (i > frames - fadeOut/2) { + mult = (mult * float((frames + fadeOut/2) - i)) / float(fadeOut); + } + float val = m_channelBuffer[sourceChannel][i]; + if (i >= got) val = 0.f; + buffer[c][i] += mult * val; + } + } + + return got; +} + +sv_frame_t +AudioGenerator::mixClipModel(Model *model, + sv_frame_t startFrame, sv_frame_t frames, + float **buffer, float gain, float pan) +{ + ClipMixer *clipMixer = m_clipMixerMap[model]; + if (!clipMixer) return 0; + + int blocks = int(frames / m_processingBlockSize); + + //!!! todo: the below -- it matters + + //!!! hang on -- the fact that the audio callback play source's + //buffer is a multiple of the plugin's buffer size doesn't mean + //that we always get called for a multiple of it here (because it + //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 + + sv_frame_t got = blocks * m_processingBlockSize; + +#ifdef DEBUG_AUDIO_GENERATOR + cout << "mixModel [clip]: start " << startFrame << ", frames " << frames + << ", blocks " << blocks << ", have " << m_noteOffs.size() + << " note-offs" << endl; +#endif + + ClipMixer::NoteStart on; + ClipMixer::NoteEnd off; + + NoteOffSet ¬eOffs = m_noteOffs[model]; + + float **bufferIndexes = new float *[m_targetChannelCount]; + + for (int i = 0; i < blocks; ++i) { + + sv_frame_t reqStart = startFrame + i * m_processingBlockSize; + + NoteList notes; + NoteExportable *exportable = dynamic_cast(model); + if (exportable) { + notes = exportable->getNotesWithin(reqStart, + reqStart + m_processingBlockSize); + } + + std::vector starts; + std::vector ends; + + for (NoteList::const_iterator ni = notes.begin(); + ni != notes.end(); ++ni) { + + sv_frame_t noteFrame = ni->start; + + if (noteFrame < reqStart || + noteFrame >= reqStart + m_processingBlockSize) continue; + + while (noteOffs.begin() != noteOffs.end() && + noteOffs.begin()->frame <= noteFrame) { + + sv_frame_t eventFrame = noteOffs.begin()->frame; + if (eventFrame < reqStart) eventFrame = reqStart; + + off.frameOffset = eventFrame - reqStart; + off.frequency = noteOffs.begin()->frequency; + +#ifdef DEBUG_AUDIO_GENERATOR + cerr << "mixModel [clip]: adding note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; +#endif + + ends.push_back(off); + noteOffs.erase(noteOffs.begin()); + } + + on.frameOffset = noteFrame - reqStart; + on.frequency = ni->getFrequency(); + on.level = float(ni->velocity) / 127.0f; + on.pan = pan; + +#ifdef DEBUG_AUDIO_GENERATOR + cout << "mixModel [clip]: adding note at frame " << noteFrame << ", frame offset " << on.frameOffset << " frequency " << on.frequency << ", level " << on.level << endl; +#endif + + starts.push_back(on); + noteOffs.insert + (NoteOff(on.frequency, noteFrame + ni->duration)); + } + + while (noteOffs.begin() != noteOffs.end() && + noteOffs.begin()->frame <= reqStart + m_processingBlockSize) { + + sv_frame_t eventFrame = noteOffs.begin()->frame; + if (eventFrame < reqStart) eventFrame = reqStart; + + off.frameOffset = eventFrame - reqStart; + off.frequency = noteOffs.begin()->frequency; + +#ifdef DEBUG_AUDIO_GENERATOR + cerr << "mixModel [clip]: adding leftover note-off at frame " << eventFrame << " frame offset " << off.frameOffset << " frequency " << off.frequency << endl; +#endif + + ends.push_back(off); + noteOffs.erase(noteOffs.begin()); + } + + for (int c = 0; c < m_targetChannelCount; ++c) { + bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; + } + + clipMixer->mix(bufferIndexes, gain, starts, ends); + } + + delete[] bufferIndexes; + + return got; +} + +sv_frame_t +AudioGenerator::mixContinuousSynthModel(Model *model, + sv_frame_t startFrame, + sv_frame_t frames, + float **buffer, + float gain, + float pan) +{ + ContinuousSynth *synth = m_continuousSynthMap[model]; + if (!synth) return 0; + + // only type we support here at the moment + SparseTimeValueModel *stvm = qobject_cast(model); + if (stvm->getScaleUnits() != "Hz") return 0; + + int blocks = int(frames / m_processingBlockSize); + + //!!! todo: see comment in mixClipModel + + sv_frame_t got = blocks * m_processingBlockSize; + +#ifdef DEBUG_AUDIO_GENERATOR + cout << "mixModel [synth]: frames " << frames + << ", blocks " << blocks << endl; +#endif + + float **bufferIndexes = new float *[m_targetChannelCount]; + + for (int i = 0; i < blocks; ++i) { + + sv_frame_t reqStart = startFrame + i * m_processingBlockSize; + + for (int c = 0; c < m_targetChannelCount; ++c) { + bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; + } + + SparseTimeValueModel::PointList points = + stvm->getPoints(reqStart, reqStart + m_processingBlockSize); + + // by default, repeat last frequency + float f0 = 0.f; + + // go straight to the last freq that is genuinely in this range + for (SparseTimeValueModel::PointList::const_iterator itr = points.end(); + itr != points.begin(); ) { + --itr; + if (itr->frame >= reqStart && + itr->frame < reqStart + m_processingBlockSize) { + f0 = itr->value; + break; + } + } + + // if we found no such frequency and the next point is further + // away than twice the model resolution, go silent (same + // criterion TimeValueLayer uses for ending a discrete curve + // segment) + if (f0 == 0.f) { + SparseTimeValueModel::PointList nextPoints = + stvm->getNextPoints(reqStart + m_processingBlockSize); + if (nextPoints.empty() || + nextPoints.begin()->frame > reqStart + 2 * stvm->getResolution()) { + f0 = -1.f; + } + } + +// cerr << "f0 = " << f0 << endl; + + synth->mix(bufferIndexes, + gain, + pan, + f0); + } + + delete[] bufferIndexes; + + return got; +} + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/AudioGenerator.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/AudioGenerator.h Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,168 @@ +/* -*- 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 FlexiNoteModel; +class DenseTimeValueModel; +class SparseOneDimensionalModel; +class Playable; +class ClipMixer; +class ContinuousSynth; + +#include +#include + +#include +#include +#include + +#include "base/BaseTypes.h" + +class AudioGenerator : public QObject +{ + Q_OBJECT + +public: + AudioGenerator(); + virtual ~AudioGenerator(); + + /** + * Add a data model to be played from and initialise any necessary + * audio generation code. Returns true if the model will be + * played. 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 buffers 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(int channelCount); + + /** + * Return the internal processing block size. The frameCount + * argument to all mixModel calls must be a multiple of this + * value. + */ + virtual sv_frame_t getBlockSize() const; + + /** + * Mix a single model into an output buffer. + */ + virtual sv_frame_t mixModel(Model *model, sv_frame_t startFrame, sv_frame_t frameCount, + float **buffer, sv_frame_t fadeIn = 0, sv_frame_t fadeOut = 0); + + /** + * Specify that only the given set of models should be played. + */ + virtual void setSoloModelSet(std::sets); + + /** + * Specify that all models should be played as normal (if not + * muted). + */ + virtual void clearSoloModelSet(); + +protected slots: + void playClipIdChanged(const Playable *, QString); + +protected: + sv_samplerate_t m_sourceSampleRate; + int m_targetChannelCount; + int m_waveType; + + bool m_soloing; + std::set m_soloModelSet; + + struct NoteOff { + + NoteOff(float _freq, sv_frame_t _frame) : frequency(_freq), frame(_frame) { } + + float frequency; + sv_frame_t frame; + + struct Comparator { + bool operator()(const NoteOff &n1, const NoteOff &n2) const { + return n1.frame < n2.frame; + } + }; + }; + + + typedef std::map ClipMixerMap; + + typedef std::multiset NoteOffSet; + typedef std::map NoteOffMap; + + typedef std::map ContinuousSynthMap; + + QMutex m_mutex; + + ClipMixerMap m_clipMixerMap; + NoteOffMap m_noteOffs; + static QString m_sampleDir; + + ContinuousSynthMap m_continuousSynthMap; + + bool usesClipMixer(const Model *); + bool wantsQuieterClips(const Model *); + bool usesContinuousSynth(const Model *); + + ClipMixer *makeClipMixerFor(const Model *model); + ContinuousSynth *makeSynthFor(const Model *model); + + static void initialiseSampleDir(); + + virtual sv_frame_t mixDenseTimeValueModel + (DenseTimeValueModel *model, sv_frame_t startFrame, sv_frame_t frameCount, + float **buffer, float gain, float pan, sv_frame_t fadeIn, sv_frame_t fadeOut); + + virtual sv_frame_t mixClipModel + (Model *model, sv_frame_t startFrame, sv_frame_t frameCount, + float **buffer, float gain, float pan); + + virtual sv_frame_t mixContinuousSynthModel + (Model *model, sv_frame_t startFrame, sv_frame_t frameCount, + float **buffer, float gain, float pan); + + static const sv_frame_t m_processingBlockSize; + + float **m_channelBuffer; + sv_frame_t m_channelBufSiz; + int m_channelBufCount; +}; + +#endif + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/AudioRecordTarget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/AudioRecordTarget.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,193 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioRecordTarget.h" + +#include "base/ViewManagerBase.h" +#include "base/TempDirectory.h" + +#include "data/model/WritableWaveFileModel.h" + +#include + +AudioRecordTarget::AudioRecordTarget(ViewManagerBase *manager, + QString clientName) : + m_viewManager(manager), + m_clientName(clientName.toUtf8().data()), + m_recording(false), + m_recordSampleRate(44100), + m_frameCount(0), + m_model(0) +{ +} + +AudioRecordTarget::~AudioRecordTarget() +{ + QMutexLocker locker(&m_mutex); +} + +void +AudioRecordTarget::setSystemRecordBlockSize(int) +{ +} + +void +AudioRecordTarget::setSystemRecordSampleRate(int n) +{ + m_recordSampleRate = n; +} + +void +AudioRecordTarget::setSystemRecordLatency(int) +{ +} + +void +AudioRecordTarget::putSamples(int nframes, float **samples) +{ + bool secChanged = false; + sv_frame_t frameToEmit = 0; + + { + QMutexLocker locker(&m_mutex); //!!! bad here + if (!m_recording) return; + + m_model->addSamples(samples, nframes); + + sv_frame_t priorFrameCount = m_frameCount; + m_frameCount += nframes; + + RealTime priorRT = RealTime::frame2RealTime + (priorFrameCount, m_recordSampleRate); + RealTime postRT = RealTime::frame2RealTime + (m_frameCount, m_recordSampleRate); + + secChanged = (postRT.sec > priorRT.sec); + if (secChanged) frameToEmit = m_frameCount; + } + + if (secChanged) { + emit recordDurationChanged(frameToEmit, m_recordSampleRate); + } +} + +void +AudioRecordTarget::setInputLevels(float, float) +{ +} + +void +AudioRecordTarget::modelAboutToBeDeleted() +{ + QMutexLocker locker(&m_mutex); + if (sender() == m_model) { + m_model = 0; + m_recording = false; + } +} + +QString +AudioRecordTarget::getRecordContainerFolder() +{ + QDir parent(TempDirectory::getInstance()->getContainingPath()); + QString subdirname("recorded"); + + if (!parent.mkpath(subdirname)) { + cerr << "ERROR: AudioRecordTarget::getRecordContainerFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl; + return ""; + } else { + return parent.filePath(subdirname); + } +} + +QString +AudioRecordTarget::getRecordFolder() +{ + QDir parent(getRecordContainerFolder()); + QDateTime now = QDateTime::currentDateTime(); + QString subdirname = QString("%1").arg(now.toString("yyyyMMdd")); + + if (!parent.mkpath(subdirname)) { + cerr << "ERROR: AudioRecordTarget::getRecordFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl; + return ""; + } else { + return parent.filePath(subdirname); + } +} + +WritableWaveFileModel * +AudioRecordTarget::startRecording() +{ + { + QMutexLocker locker(&m_mutex); + + if (m_recording) { + cerr << "WARNING: AudioRecordTarget::startRecording: We are already recording" << endl; + return 0; + } + + m_model = 0; + m_frameCount = 0; + + QString folder = getRecordFolder(); + if (folder == "") return 0; + QDir recordedDir(folder); + + QDateTime now = QDateTime::currentDateTime(); + + // Don't use QDateTime::toString(Qt::ISODate) as the ":" character + // isn't permitted in filenames on Windows + QString filename = QString("recorded-%1.wav") + .arg(now.toString("yyyyMMdd-HHmmss-zzz")); + + m_audioFileName = recordedDir.filePath(filename); + + m_model = new WritableWaveFileModel(m_recordSampleRate, 2, m_audioFileName); + + if (!m_model->isOK()) { + cerr << "ERROR: AudioRecordTarget::startRecording: Recording failed" + << endl; + //!!! and throw? + delete m_model; + m_model = 0; + return 0; + } + + m_recording = true; + } + + emit recordStatusChanged(true); + return m_model; +} + +void +AudioRecordTarget::stopRecording() +{ + { + QMutexLocker locker(&m_mutex); + if (!m_recording) { + cerr << "WARNING: AudioRecordTarget::startRecording: Not recording" << endl; + return; + } + + m_model->writeComplete(); + m_model = 0; + m_recording = false; + } + + emit recordStatusChanged(false); + emit recordCompleted(); +} + + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/AudioRecordTarget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/AudioRecordTarget.h Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef AUDIO_RECORD_TARGET_H +#define AUDIO_RECORD_TARGET_H + +#include + +#include + +#include +#include + +#include "base/BaseTypes.h" + +class ViewManagerBase; +class WritableWaveFileModel; + +class AudioRecordTarget : public QObject, + public breakfastquay::ApplicationRecordTarget +{ + Q_OBJECT + +public: + AudioRecordTarget(ViewManagerBase *, QString clientName); + virtual ~AudioRecordTarget(); + + virtual std::string getClientName() const { return m_clientName; } + + virtual int getApplicationSampleRate() const { return 0; } // don't care + virtual int getApplicationChannelCount() const { return 2; } + + virtual void setSystemRecordBlockSize(int); + virtual void setSystemRecordSampleRate(int); + virtual void setSystemRecordLatency(int); + + virtual void putSamples(int nframes, float **samples); + + virtual void setInputLevels(float peakLeft, float peakRight); + + virtual void audioProcessingOverload() { } + + QString getRecordContainerFolder(); + QString getRecordFolder(); + + bool isRecording() const { return m_recording; } + WritableWaveFileModel *startRecording(); // caller takes ownership + void stopRecording(); + +signals: + void recordStatusChanged(bool recording); + void recordDurationChanged(sv_frame_t, sv_samplerate_t); // emitted occasionally + void recordCompleted(); + +protected slots: + void modelAboutToBeDeleted(); + +private: + ViewManagerBase *m_viewManager; + std::string m_clientName; + bool m_recording; + sv_samplerate_t m_recordSampleRate; + sv_frame_t m_frameCount; + QString m_audioFileName; + WritableWaveFileModel *m_model; + QMutex m_mutex; +}; + +#endif diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/ClipMixer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/ClipMixer.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,248 @@ +/* -*- 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, 2006-2014 QMUL. + + 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 "ClipMixer.h" + +#include +#include + +#include "base/Debug.h" + +//#define DEBUG_CLIP_MIXER 1 + +ClipMixer::ClipMixer(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize) : + m_channels(channels), + m_sampleRate(sampleRate), + m_blockSize(blockSize), + m_clipData(0), + m_clipLength(0), + m_clipF0(0), + m_clipRate(0) +{ +} + +ClipMixer::~ClipMixer() +{ + if (m_clipData) free(m_clipData); +} + +void +ClipMixer::setChannelCount(int channels) +{ + m_channels = channels; +} + +bool +ClipMixer::loadClipData(QString path, double f0, double level) +{ + if (m_clipData) { + cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl; + return false; + } + + SF_INFO info; + SNDFILE *file; + float *tmpFrames; + sv_frame_t i; + + info.format = 0; + file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); + if (!file) { + cerr << "ClipMixer::loadClipData: Failed to open file path \"" + << path << "\": " << sf_strerror(file) << endl; + return false; + } + + tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float)); + if (!tmpFrames) { + cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl; + return false; + } + + sf_readf_float(file, tmpFrames, info.frames); + sf_close(file); + + m_clipData = (float *)malloc(info.frames * sizeof(float)); + if (!m_clipData) { + cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl; + free(tmpFrames); + return false; + } + + for (i = 0; i < info.frames; ++i) { + int j; + m_clipData[i] = 0.0f; + for (j = 0; j < info.channels; ++j) { + m_clipData[i] += tmpFrames[i * info.channels + j] * float(level); + } + } + + free(tmpFrames); + + m_clipLength = info.frames; + m_clipF0 = f0; + m_clipRate = info.samplerate; + + return true; +} + +void +ClipMixer::reset() +{ + m_playing.clear(); +} + +double +ClipMixer::getResampleRatioFor(double frequency) +{ + if (!m_clipData || !m_clipRate) return 1.0; + double pitchRatio = m_clipF0 / frequency; + double resampleRatio = m_sampleRate / m_clipRate; + return pitchRatio * resampleRatio; +} + +sv_frame_t +ClipMixer::getResampledClipDuration(double frequency) +{ + return sv_frame_t(ceil(double(m_clipLength) * getResampleRatioFor(frequency))); +} + +void +ClipMixer::mix(float **toBuffers, + float gain, + std::vector newNotes, + std::vector endingNotes) +{ + foreach (NoteStart note, newNotes) { + if (note.frequency > 20 && + note.frequency < 5000) { + m_playing.push_back(note); + } + } + + std::vector remaining; + + float *levels = new float[m_channels]; + +#ifdef DEBUG_CLIP_MIXER + cerr << "ClipMixer::mix: have " << m_playing.size() << " playing note(s)" + << " and " << endingNotes.size() << " note(s) ending here" + << endl; +#endif + + foreach (NoteStart note, m_playing) { + + for (int c = 0; c < m_channels; ++c) { + levels[c] = note.level * gain; + } + if (note.pan != 0.0 && m_channels == 2) { + levels[0] *= 1.0f - note.pan; + levels[1] *= note.pan + 1.0f; + } + + sv_frame_t start = note.frameOffset; + sv_frame_t durationHere = m_blockSize; + if (start > 0) durationHere = m_blockSize - start; + + bool ending = false; + + foreach (NoteEnd end, endingNotes) { + if (end.frequency == note.frequency && + end.frameOffset >= start && + end.frameOffset <= m_blockSize) { + ending = true; + durationHere = end.frameOffset; + if (start > 0) durationHere = end.frameOffset - start; + break; + } + } + + sv_frame_t clipDuration = getResampledClipDuration(note.frequency); + if (start + clipDuration > 0) { + if (start < 0 && start + clipDuration < durationHere) { + durationHere = start + clipDuration; + } + if (durationHere > 0) { + mixNote(toBuffers, + levels, + note.frequency, + start < 0 ? -start : 0, + start > 0 ? start : 0, + durationHere, + ending); + } + } + + if (!ending) { + NoteStart adjusted = note; + adjusted.frameOffset -= m_blockSize; + remaining.push_back(adjusted); + } + } + + delete[] levels; + + m_playing = remaining; +} + +void +ClipMixer::mixNote(float **toBuffers, + float *levels, + float frequency, + sv_frame_t sourceOffset, + sv_frame_t targetOffset, + sv_frame_t sampleCount, + bool isEnd) +{ + if (!m_clipData) return; + + double ratio = getResampleRatioFor(frequency); + + double releaseTime = 0.01; + sv_frame_t releaseSampleCount = sv_frame_t(round(releaseTime * m_sampleRate)); + if (releaseSampleCount > sampleCount) { + releaseSampleCount = sampleCount; + } + double releaseFraction = 1.0/double(releaseSampleCount); + + for (sv_frame_t i = 0; i < sampleCount; ++i) { + + sv_frame_t s = sourceOffset + i; + + double os = double(s) / ratio; + sv_frame_t osi = sv_frame_t(floor(os)); + + //!!! just linear interpolation for now (same as SV's sample + //!!! player). a small sinc kernel would be better and + //!!! probably "good enough" + double value = 0.0; + if (osi < m_clipLength) { + value += m_clipData[osi]; + } + if (osi + 1 < m_clipLength) { + value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - double(osi)); + } + + if (isEnd && i + releaseSampleCount > sampleCount) { + value *= releaseFraction * double(sampleCount - i); // linear ramp for release + } + + for (int c = 0; c < m_channels; ++c) { + toBuffers[c][targetOffset + i] += float(levels[c] * value); + } + } +} + + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/ClipMixer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/ClipMixer.h Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,94 @@ +/* -*- 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, 2006-2014 QMUL. + + 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 CLIP_MIXER_H +#define CLIP_MIXER_H + +#include +#include + +#include "base/BaseTypes.h" + +/** + * Mix in synthetic notes produced by resampling a prerecorded + * clip. (i.e. this is an implementation of a digital sampler in the + * musician's sense.) This can mix any number of notes of arbitrary + * frequency, so long as they all use the same sample clip. + */ + +class ClipMixer +{ +public: + ClipMixer(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize); + ~ClipMixer(); + + void setChannelCount(int channels); + + /** + * Load a sample clip from a wav file. This can only happen once: + * construct a new ClipMixer if you want a different clip. The + * clip was recorded at a pitch with fundamental frequency clipF0, + * and should be scaled by level (in the range 0-1) when playing + * back. + */ + bool loadClipData(QString clipFilePath, double clipF0, double level); + + void reset(); // discarding any playing notes + + struct NoteStart { + sv_frame_t frameOffset; // within current processing block + float frequency; // Hz + float level; // volume in range (0,1] + float pan; // range [-1,1] + }; + + struct NoteEnd { + sv_frame_t frameOffset; // in current processing block + float frequency; // matching note start + }; + + void mix(float **toBuffers, + float gain, + std::vector newNotes, + std::vector endingNotes); + +private: + int m_channels; + sv_samplerate_t m_sampleRate; + sv_frame_t m_blockSize; + + QString m_clipPath; + + float *m_clipData; + sv_frame_t m_clipLength; + double m_clipF0; + sv_samplerate_t m_clipRate; + + std::vector m_playing; + + double getResampleRatioFor(double frequency); + sv_frame_t getResampledClipDuration(double frequency); + + void mixNote(float **toBuffers, + float *levels, + float frequency, + sv_frame_t sourceOffset, // within resampled note + sv_frame_t targetOffset, // within target buffer + sv_frame_t sampleCount, + bool isEnd); +}; + + +#endif diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/ContinuousSynth.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/ContinuousSynth.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,149 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ContinuousSynth.h" + +#include "base/Debug.h" +#include "system/System.h" + +#include + +ContinuousSynth::ContinuousSynth(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize, int waveType) : + m_channels(channels), + m_sampleRate(sampleRate), + m_blockSize(blockSize), + m_prevF0(-1.0), + m_phase(0.0), + m_wavetype(waveType) // 0: 3 sinusoids, 1: 1 sinusoid, 2: sawtooth, 3: square +{ +} + +ContinuousSynth::~ContinuousSynth() +{ +} + +void +ContinuousSynth::reset() +{ + m_phase = 0; +} + +void +ContinuousSynth::mix(float **toBuffers, float gain, float pan, float f0f) +{ + double f0(f0f); + if (f0 == 0.0) f0 = m_prevF0; + + bool wasOn = (m_prevF0 > 0.0); + bool nowOn = (f0 > 0.0); + + if (!nowOn && !wasOn) { + m_phase = 0; + return; + } + + sv_frame_t fadeLength = 100; + + float *levels = new float[m_channels]; + + for (int c = 0; c < m_channels; ++c) { + levels[c] = gain * 0.5f; // scale gain otherwise too loud compared to source + } + if (pan != 0.0 && m_channels == 2) { + levels[0] *= 1.0f - pan; + levels[1] *= pan + 1.0f; + } + +// cerr << "ContinuousSynth::mix: f0 = " << f0 << " (from " << m_prevF0 << "), phase = " << m_phase << endl; + + for (sv_frame_t i = 0; i < m_blockSize; ++i) { + + double fHere = (nowOn ? f0 : m_prevF0); + + if (wasOn && nowOn && (f0 != m_prevF0) && (i < fadeLength)) { + // interpolate the frequency shift + fHere = m_prevF0 + ((f0 - m_prevF0) * double(i)) / double(fadeLength); + } + + double phasor = (fHere * 2 * M_PI) / m_sampleRate; + + m_phase = m_phase + phasor; + + int harmonics = int((m_sampleRate / 4) / fHere - 1); + if (harmonics < 1) harmonics = 1; + + switch (m_wavetype) { + case 1: + harmonics = 1; + break; + case 2: + break; + case 3: + break; + default: + harmonics = 3; + break; + } + + for (int h = 0; h < harmonics; ++h) { + + double v = 0; + double hn = 0; + double hp = 0; + + switch (m_wavetype) { + case 1: // single sinusoid + v = sin(m_phase); + break; + case 2: // sawtooth + if (h != 0) { + hn = h + 1; + hp = m_phase * hn; + v = -(1.0 / M_PI) * sin(hp) / hn; + } else { + v = 0.5; + } + break; + case 3: // square + hn = h*2 + 1; + hp = m_phase * hn; + v = sin(hp) / hn; + break; + default: // 3 sinusoids + hn = h + 1; + hp = m_phase * hn; + v = sin(hp) / hn; + break; + } + + if (!wasOn && i < fadeLength) { + // fade in + v = v * (double(i) / double(fadeLength)); + } else if (!nowOn) { + // fade out + if (i > fadeLength) v = 0; + else v = v * (1.0 - (double(i) / double(fadeLength))); + } + + for (int c = 0; c < m_channels; ++c) { + toBuffers[c][i] += float(levels[c] * v); + } + } + } + + m_prevF0 = f0; + + delete[] levels; +} + diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/ContinuousSynth.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/ContinuousSynth.h Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,65 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CONTINUOUS_SYNTH_H +#define CONTINUOUS_SYNTH_H + +#include "base/BaseTypes.h" + +/** + * Mix into a target buffer a signal synthesised so as to sound at a + * specific frequency. The frequency may change with each processing + * block, or may be switched on or off. + */ + +class ContinuousSynth +{ +public: + ContinuousSynth(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize, int waveType); + ~ContinuousSynth(); + + void setChannelCount(int channels); + + void reset(); + + /** + * Mix in a signal to be heard at the given fundamental + * frequency. Any oscillator state will be maintained between + * process calls so as to provide a continuous sound. The f0 value + * may vary between calls. + * + * Supply f0 equal to 0 if you want to maintain the f0 from the + * previous block (without having to remember what it was). + * + * Supply f0 less than 0 for silence. You should continue to call + * this even when the signal is silent if you want to ensure the + * sound switches on and off cleanly. + */ + void mix(float **toBuffers, + float gain, + float pan, + float f0); + +private: + int m_channels; + sv_samplerate_t m_sampleRate; + sv_frame_t m_blockSize; + + double m_prevF0; + double m_phase; + + int m_wavetype; +}; + +#endif diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/PlaySpeedRangeMapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/PlaySpeedRangeMapper.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,101 @@ +/* -*- 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 QMUL. + + 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 "PlaySpeedRangeMapper.h" + +#include +#include + +// PlaySpeedRangeMapper maps a position in the range [0,120] on to a +// play speed factor on a logarithmic scale in the range 0.125 -> +// 8. This ensures that the desirable speed factors 0.25, 0.5, 1, 2, +// and 4 are all mapped to exact positions (respectively 20, 40, 60, +// 80, 100). + +// Note that the "factor" referred to below is a play speed factor +// (higher = faster, 1.0 = normal speed), the "value" is a percentage +// (higher = faster, 100 = normal speed), and the "position" is an +// integer step on the dial's scale (0-120, 60 = centre). + +PlaySpeedRangeMapper::PlaySpeedRangeMapper() : + m_minpos(0), + m_maxpos(120) +{ +} + +int +PlaySpeedRangeMapper::getPositionForValue(double value) const +{ + // value is percent + double factor = getFactorForValue(value); + int position = getPositionForFactor(factor); + return position; +} + +int +PlaySpeedRangeMapper::getPositionForValueUnclamped(double value) const +{ + // We don't really provide this + return getPositionForValue(value); +} + +double +PlaySpeedRangeMapper::getValueForPosition(int position) const +{ + double factor = getFactorForPosition(position); + double pc = getValueForFactor(factor); + return pc; +} + +double +PlaySpeedRangeMapper::getValueForPositionUnclamped(int position) const +{ + // We don't really provide this + return getValueForPosition(position); +} + +double +PlaySpeedRangeMapper::getValueForFactor(double factor) const +{ + return factor * 100.0; +} + +double +PlaySpeedRangeMapper::getFactorForValue(double value) const +{ + return value / 100.0; +} + +int +PlaySpeedRangeMapper::getPositionForFactor(double factor) const +{ + if (factor == 0) return m_minpos; + int pos = int(lrint((log2(factor) + 3.0) * 20.0)); + if (pos < m_minpos) pos = m_minpos; + if (pos > m_maxpos) pos = m_maxpos; + return pos; +} + +double +PlaySpeedRangeMapper::getFactorForPosition(int position) const +{ + return pow(2.0, double(position) * 0.05 - 3.0); +} + +QString +PlaySpeedRangeMapper::getUnit() const +{ + return "%"; +} diff -r 428ce32a8dd9 -r a2a8fa0eed08 audio/PlaySpeedRangeMapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/PlaySpeedRangeMapper.h Wed Apr 20 12:06:28 2016 +0100 @@ -0,0 +1,49 @@ +/* -*- 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 QMUL. + + 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 _PLAY_SPEED_RANGE_MAPPER_H_ +#define _PLAY_SPEED_RANGE_MAPPER_H_ + +#include "base/RangeMapper.h" + +class PlaySpeedRangeMapper : public RangeMapper +{ +public: + PlaySpeedRangeMapper(); + + int getMinPosition() const { return m_minpos; } + int getMaxPosition() const { return m_maxpos; } + + virtual int getPositionForValue(double value) const; + virtual int getPositionForValueUnclamped(double value) const; + + virtual double getValueForPosition(int position) const; + virtual double getValueForPositionUnclamped(int position) const; + + int getPositionForFactor(double factor) const; + double getValueForFactor(double factor) const; + + double getFactorForPosition(int position) const; + double getFactorForValue(double value) const; + + virtual QString getUnit() const; + +protected: + int m_minpos; + int m_maxpos; +}; + + +#endif diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioCallbackPlaySource.cpp --- a/audioio/AudioCallbackPlaySource.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1870 +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 and QMUL. - - 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 "data/model/Model.h" -#include "base/ViewManagerBase.h" -#include "base/PlayParameterRepository.h" -#include "base/Preferences.h" -#include "data/model/DenseTimeValueModel.h" -#include "data/model/WaveFileModel.h" -#include "data/model/SparseOneDimensionalModel.h" -#include "plugin/RealTimePluginInstance.h" - -#include "AudioCallbackPlayTarget.h" - -#include -using namespace RubberBand; - -#include -#include - -//#define DEBUG_AUDIO_PLAY_SOURCE 1 -//#define DEBUG_AUDIO_PLAY_SOURCE_PLAYING 1 - -static const size_t DEFAULT_RING_BUFFER_SIZE = 131071; - -AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManagerBase *manager, - QString clientName) : - m_viewManager(manager), - m_audioGenerator(new AudioGenerator()), - m_clientName(clientName), - 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_target(0), - m_lastRetrievalTimestamp(0.0), - m_lastRetrievedBlockSize(0), - m_trustworthyTimestamps(true), - m_lastCurrentFrame(0), - m_playing(false), - m_exiting(false), - m_lastModelEndFrame(0), - m_ringBufferSize(DEFAULT_RING_BUFFER_SIZE), - m_outputLeft(0.0), - m_outputRight(0.0), - m_auditioningPlugin(0), - m_auditioningPluginBypassed(false), - m_playStartFrame(0), - m_playStartFramePassed(false), - m_timeStretcher(0), - m_monoStretcher(0), - m_stretchRatio(1.0), - m_stretcherInputCount(0), - m_stretcherInputs(0), - m_stretcherInputSizes(0), - m_fillThread(0), - m_converter(0), - m_crapConverter(0), - m_resampleQuality(Preferences::getInstance()->getResampleQuality()) -{ - 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(this, SIGNAL(playStatusChanged(bool)), - m_viewManager, SLOT(playStatusChanged(bool))); - - connect(PlayParameterRepository::getInstance(), - SIGNAL(playParametersChanged(PlayParameters *)), - this, SLOT(playParametersChanged(PlayParameters *))); - - connect(Preferences::getInstance(), - SIGNAL(propertyChanged(PropertyContainer::PropertyName)), - this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); -} - -AudioCallbackPlaySource::~AudioCallbackPlaySource() -{ -#ifdef DEBUG_AUDIO_PLAY_SOURCE - SVDEBUG << "AudioCallbackPlaySource::~AudioCallbackPlaySource entering" << endl; -#endif - m_exiting = true; - - if (m_fillThread) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource dtor: awakening thread" << endl; -#endif - m_condition.wakeAll(); - m_fillThread->wait(); - delete m_fillThread; - } - - clearModels(); - - if (m_readBuffers != m_writeBuffers) { - delete m_readBuffers; - } - - delete m_writeBuffers; - - delete m_audioGenerator; - - for (size_t i = 0; i < m_stretcherInputCount; ++i) { - delete[] m_stretcherInputs[i]; - } - delete[] m_stretcherInputSizes; - delete[] m_stretcherInputs; - - delete m_timeStretcher; - delete m_monoStretcher; - - m_bufferScavenger.scavenge(true); - m_pluginScavenger.scavenge(true); -#ifdef DEBUG_AUDIO_PLAY_SOURCE - SVDEBUG << "AudioCallbackPlaySource::~AudioCallbackPlaySource finishing" << endl; -#endif -} - -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(model); - if (dtvm) modelChannels = dtvm->getChannelCount(); - if (modelChannels > m_sourceChannelCount) { - m_sourceChannelCount = modelChannels; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Adding model with " << modelChannels << " channels at rate " << model->getSampleRate() << endl; -#endif - - 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::const_iterator i = m_models.begin(); - i != m_models.end(); ++i) { - // Only wave file models can be considered conflicting -- - // writable wave file models are derived and we shouldn't - // take their rates into account. Also, don't give any - // particular weight to a file that's already playing at - // the wrong rate anyway - WaveFileModel *wfm = dynamic_cast(*i); - if (wfm && wfm != dtvm && - wfm->getSampleRate() != model->getSampleRate() && - wfm->getSampleRate() == m_sourceSampleRate) { - SVDEBUG << "AudioCallbackPlaySource::addModel: Conflicting wave file model " << *i << " found" << endl; - conflicting = true; - break; - } - } - - if (conflicting) { - - SVDEBUG << "AudioCallbackPlaySource::addModel: ERROR: " - << "New model sample rate does not match" << endl - << "existing model(s) (new " << model->getSampleRate() - << " vs " << m_sourceSampleRate - << "), playback will be wrong" - << 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); - src_delete(m_crapConverter); - m_converter = 0; - m_crapConverter = 0; - } - } - - rebuildRangeLists(); - - m_mutex.unlock(); - - m_audioGenerator->setTargetChannelCount(getTargetChannelCount()); - - if (!m_fillThread) { - m_fillThread = new FillThread(*this); - m_fillThread->start(); - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s) -- emitting modelReplaced" << endl; -#endif - - if (buffersChanged || srChanged) { - emit modelReplaced(); - } - - connect(model, SIGNAL(modelChanged(size_t, size_t)), - this, SLOT(modelChanged(size_t, size_t))); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::addModel: awakening thread" << endl; -#endif - - m_condition.wakeAll(); -} - -void -AudioCallbackPlaySource::modelChanged(size_t startFrame, size_t endFrame) -{ -#ifdef DEBUG_AUDIO_PLAY_SOURCE - SVDEBUG << "AudioCallbackPlaySource::modelChanged(" << startFrame << "," << endFrame << ")" << endl; -#endif - if (endFrame > m_lastModelEndFrame) { - m_lastModelEndFrame = endFrame; - rebuildRangeLists(); - } -} - -void -AudioCallbackPlaySource::removeModel(Model *model) -{ - m_mutex.lock(); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::removeModel(" << model << ")" << endl; -#endif - - disconnect(model, SIGNAL(modelChanged(size_t, size_t)), - this, SLOT(modelChanged(size_t, size_t))); - - m_models.erase(model); - - if (m_models.empty()) { - if (m_converter) { - src_delete(m_converter); - src_delete(m_crapConverter); - m_converter = 0; - m_crapConverter = 0; - } - m_sourceSampleRate = 0; - } - - size_t lastEnd = 0; - for (std::set::const_iterator i = m_models.begin(); - i != m_models.end(); ++i) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::removeModel(" << model << "): checking end frame on model " << *i << endl; -#endif - if ((*i)->getEndFrame() > lastEnd) lastEnd = (*i)->getEndFrame(); -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "(done, lastEnd now " << lastEnd << ")" << endl; -#endif - } - m_lastModelEndFrame = lastEnd; - - m_audioGenerator->removeModel(model); - - m_mutex.unlock(); - - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::clearModels() -{ - m_mutex.lock(); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::clearModels()" << endl; -#endif - - m_models.clear(); - - if (m_converter) { - src_delete(m_converter); - src_delete(m_crapConverter); - m_converter = 0; - m_crapConverter = 0; - } - - m_lastModelEndFrame = 0; - - m_sourceSampleRate = 0; - - m_mutex.unlock(); - - m_audioGenerator->clearModels(); - - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::clearRingBuffers(bool haveLock, size_t count) -{ - if (!haveLock) m_mutex.lock(); - - rebuildRangeLists(); - - if (count == 0) { - if (m_writeBuffers) count = m_writeBuffers->size(); - } - - m_writeBufferFill = getCurrentBufferedFrame(); - - 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(m_ringBufferSize)); - } - -// cout << "AudioCallbackPlaySource::clearRingBuffers: Created " -// << count << " write buffers" << endl; - - if (!haveLock) { - m_mutex.unlock(); - } -} - -void -AudioCallbackPlaySource::play(size_t startFrame) -{ - if (m_viewManager->getPlaySelectionMode() && - !m_viewManager->getSelections().empty()) { - - SVDEBUG << "AudioCallbackPlaySource::play: constraining frame " << startFrame << " to selection = "; - - startFrame = m_viewManager->constrainFrameToSelection(startFrame); - - SVDEBUG << startFrame << endl; - - } else { - if (startFrame >= m_lastModelEndFrame) { - startFrame = 0; - } - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "play(" << startFrame << ") -> playback model "; -#endif - - startFrame = m_viewManager->alignReferenceToPlaybackFrame(startFrame); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << startFrame << endl; -#endif - - // 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. - // NO -- we can end up playing some first -- always reset here - - m_mutex.lock(); - - if (m_timeStretcher) { - m_timeStretcher->reset(); - } - if (m_monoStretcher) { - m_monoStretcher->reset(); - } - - m_readBufferFill = m_writeBufferFill = startFrame; - if (m_readBuffers) { - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer *rb = getReadRingBuffer(c); -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "reset ring buffer for channel " << c << endl; -#endif - if (rb) rb->reset(); - } - } - if (m_converter) src_reset(m_converter); - if (m_crapConverter) src_reset(m_crapConverter); - - m_mutex.unlock(); - - m_audioGenerator->reset(); - - m_playStartFrame = startFrame; - m_playStartFramePassed = false; - m_playStartedAt = RealTime::zeroTime; - if (m_target) { - m_playStartedAt = RealTime::fromSeconds(m_target->getCurrentTime()); - } - - bool changed = !m_playing; - m_lastRetrievalTimestamp = 0; - m_lastCurrentFrame = 0; - m_playing = true; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::play: awakening thread" << endl; -#endif - - m_condition.wakeAll(); - if (changed) { - emit playStatusChanged(m_playing); - emit activity(tr("Play from %1").arg - (RealTime::frame2RealTime - (m_playStartFrame, m_sourceSampleRate).toText().c_str())); - } -} - -void -AudioCallbackPlaySource::stop() -{ -#ifdef DEBUG_AUDIO_PLAY_SOURCE - SVDEBUG << "AudioCallbackPlaySource::stop()" << endl; -#endif - bool changed = m_playing; - m_playing = false; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::stop: awakening thread" << endl; -#endif - - m_condition.wakeAll(); - m_lastRetrievalTimestamp = 0; - if (changed) { - emit playStatusChanged(m_playing); - emit activity(tr("Stop at %1").arg - (RealTime::frame2RealTime - (m_lastCurrentFrame, m_sourceSampleRate).toText().c_str())); - } - m_lastCurrentFrame = 0; -} - -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 *) -{ - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName n) -{ - if (n == "Resample Quality") { - setResampleQuality(Preferences::getInstance()->getResampleQuality()); - } -} - -void -AudioCallbackPlaySource::audioProcessingOverload() -{ - cerr << "Audio processing overload!" << endl; - - if (!m_playing) return; - - RealTimePluginInstance *ap = m_auditioningPlugin; - if (ap && !m_auditioningPluginBypassed) { - m_auditioningPluginBypassed = true; - emit audioOverloadPluginDisabled(); - return; - } - - if (m_timeStretcher && - m_timeStretcher->getTimeRatio() < 1.0 && - m_stretcherInputCount > 1 && - m_monoStretcher && !m_stretchMono) { - m_stretchMono = true; - emit audioTimeStretchMultiChannelDisabled(); - return; - } -} - -void -AudioCallbackPlaySource::setTarget(AudioCallbackPlayTarget *target, size_t size) -{ - m_target = target; - cout << "AudioCallbackPlaySource::setTarget: Block size -> " << size << endl; - if (size != 0) { - m_blockSize = size; - } - if (size * 4 > m_ringBufferSize) { - SVDEBUG << "AudioCallbackPlaySource::setTarget: Buffer size " - << size << " > a quarter of ring buffer size " - << m_ringBufferSize << ", calling for more ring buffer" - << endl; - m_ringBufferSize = size * 4; - if (m_writeBuffers && !m_writeBuffers->empty()) { - clearRingBuffers(); - } - } -} - -size_t -AudioCallbackPlaySource::getTargetBlockSize() const -{ -// cout << "AudioCallbackPlaySource::getTargetBlockSize() -> " << m_blockSize << 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() -{ - // This method attempts to estimate which audio sample frame is - // "currently coming through the speakers". - - size_t targetRate = getTargetSampleRate(); - size_t latency = m_playLatency; // at target rate - RealTime latency_t = RealTime::frame2RealTime(latency, targetRate); - - return getCurrentFrame(latency_t); -} - -size_t -AudioCallbackPlaySource::getCurrentBufferedFrame() -{ - return getCurrentFrame(RealTime::zeroTime); -} - -size_t -AudioCallbackPlaySource::getCurrentFrame(RealTime latency_t) -{ - bool resample = false; - double resampleRatio = 1.0; - - // We resample when filling the ring buffer, and time-stretch when - // draining it. The buffer contains data at the "target rate" and - // the latency provided by the target is also at the target rate. - // Because of the multiple rates involved, we do the actual - // calculation using RealTime instead. - - size_t sourceRate = getSourceSampleRate(); - size_t targetRate = getTargetSampleRate(); - - if (sourceRate == 0 || targetRate == 0) return 0; - - size_t inbuffer = 0; // at target rate - - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer *rb = getReadRingBuffer(c); - if (rb) { - size_t here = rb->getReadSpace(); - if (c == 0 || here < inbuffer) inbuffer = here; - } - } - - size_t readBufferFill = m_readBufferFill; - size_t lastRetrievedBlockSize = m_lastRetrievedBlockSize; - double lastRetrievalTimestamp = m_lastRetrievalTimestamp; - double currentTime = 0.0; - if (m_target) currentTime = m_target->getCurrentTime(); - - bool looping = m_viewManager->getPlayLoopMode(); - - RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, targetRate); - - size_t stretchlat = 0; - double timeRatio = 1.0; - - if (m_timeStretcher) { - stretchlat = m_timeStretcher->getLatency(); - timeRatio = m_timeStretcher->getTimeRatio(); - } - - RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, targetRate); - - // When the target has just requested a block from us, the last - // sample it obtained was our buffer fill frame count minus the - // amount of read space (converted back to source sample rate) - // remaining now. That sample is not expected to be played until - // the target's play latency has elapsed. By the time the - // following block is requested, that sample will be at the - // target's play latency minus the last requested block size away - // from being played. - - RealTime sincerequest_t = RealTime::zeroTime; - RealTime lastretrieved_t = RealTime::zeroTime; - - if (m_target && - m_trustworthyTimestamps && - lastRetrievalTimestamp != 0.0) { - - lastretrieved_t = RealTime::frame2RealTime - (lastRetrievedBlockSize, targetRate); - - // calculate number of frames at target rate that have elapsed - // since the end of the last call to getSourceSamples - - if (m_trustworthyTimestamps && !looping) { - - // this adjustment seems to cause more problems when looping - double elapsed = currentTime - lastRetrievalTimestamp; - - if (elapsed > 0.0) { - sincerequest_t = RealTime::fromSeconds(elapsed); - } - } - - } else { - - lastretrieved_t = RealTime::frame2RealTime - (getTargetBlockSize(), targetRate); - } - - RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, sourceRate); - - if (timeRatio != 1.0) { - lastretrieved_t = lastretrieved_t / timeRatio; - sincerequest_t = sincerequest_t / timeRatio; - latency_t = latency_t / timeRatio; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "\nbuffered to: " << bufferedto_t << ", in buffer: " << inbuffer_t << ", time ratio " << timeRatio << "\n stretcher latency: " << stretchlat_t << ", device latency: " << latency_t << "\n since request: " << sincerequest_t << ", last retrieved quantity: " << lastretrieved_t << endl; -#endif - - RealTime end = RealTime::frame2RealTime(m_lastModelEndFrame, sourceRate); - - // Normally the range lists should contain at least one item each - // -- if playback is unconstrained, that item should report the - // entire source audio duration. - - if (m_rangeStarts.empty()) { - rebuildRangeLists(); - } - - if (m_rangeStarts.empty()) { - // this code is only used in case of error in rebuildRangeLists - RealTime playing_t = bufferedto_t - - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t - + sincerequest_t; - if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; - size_t frame = RealTime::realTime2Frame(playing_t, sourceRate); - return m_viewManager->alignPlaybackFrameToReference(frame); - } - - int inRange = 0; - int index = 0; - - for (size_t i = 0; i < m_rangeStarts.size(); ++i) { - if (bufferedto_t >= m_rangeStarts[i]) { - inRange = index; - } else { - break; - } - ++index; - } - - if (inRange >= m_rangeStarts.size()) inRange = m_rangeStarts.size()-1; - - RealTime playing_t = bufferedto_t; - - playing_t = playing_t - - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t - + sincerequest_t; - - // This rather gross little hack is used to ensure that latency - // compensation doesn't result in the playback pointer appearing - // to start earlier than the actual playback does. It doesn't - // work properly (hence the bail-out in the middle) because if we - // are playing a relatively short looped region, the playing time - // estimated from the buffer fill frame may have wrapped around - // the region boundary and end up being much smaller than the - // theoretical play start frame, perhaps even for the entire - // duration of playback! - - if (!m_playStartFramePassed) { - RealTime playstart_t = RealTime::frame2RealTime(m_playStartFrame, - sourceRate); - if (playing_t < playstart_t) { -// cerr << "playing_t " << playing_t << " < playstart_t " -// << playstart_t << endl; - if (/*!!! sincerequest_t > RealTime::zeroTime && */ - m_playStartedAt + latency_t + stretchlat_t < - RealTime::fromSeconds(currentTime)) { -// cerr << "but we've been playing for long enough that I think we should disregard it (it probably results from loop wrapping)" << endl; - m_playStartFramePassed = true; - } else { - playing_t = playstart_t; - } - } else { - m_playStartFramePassed = true; - } - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "playing_t " << playing_t; -#endif - - playing_t = playing_t - m_rangeStarts[inRange]; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << " as offset into range " << inRange << " (start =" << m_rangeStarts[inRange] << " duration =" << m_rangeDurations[inRange] << ") = " << playing_t << endl; -#endif - - while (playing_t < RealTime::zeroTime) { - - if (inRange == 0) { - if (looping) { - inRange = m_rangeStarts.size() - 1; - } else { - break; - } - } else { - --inRange; - } - - playing_t = playing_t + m_rangeDurations[inRange]; - } - - playing_t = playing_t + m_rangeStarts[inRange]; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << " playing time: " << playing_t << endl; -#endif - - if (!looping) { - if (inRange == m_rangeStarts.size()-1 && - playing_t >= m_rangeStarts[inRange] + m_rangeDurations[inRange]) { -cerr << "Not looping, inRange " << inRange << " == rangeStarts.size()-1, playing_t " << playing_t << " >= m_rangeStarts[inRange] " << m_rangeStarts[inRange] << " + m_rangeDurations[inRange] " << m_rangeDurations[inRange] << " -- stopping" << endl; - stop(); - } - } - - if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; - - size_t frame = RealTime::realTime2Frame(playing_t, sourceRate); - - if (m_lastCurrentFrame > 0 && !looping) { - if (frame < m_lastCurrentFrame) { - frame = m_lastCurrentFrame; - } - } - - m_lastCurrentFrame = frame; - - return m_viewManager->alignPlaybackFrameToReference(frame); -} - -void -AudioCallbackPlaySource::rebuildRangeLists() -{ - bool constrained = (m_viewManager->getPlaySelectionMode()); - - m_rangeStarts.clear(); - m_rangeDurations.clear(); - - size_t sourceRate = getSourceSampleRate(); - if (sourceRate == 0) return; - - RealTime end = RealTime::frame2RealTime(m_lastModelEndFrame, sourceRate); - if (end == RealTime::zeroTime) return; - - if (!constrained) { - m_rangeStarts.push_back(RealTime::zeroTime); - m_rangeDurations.push_back(end); - return; - } - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - MultiSelection::SelectionList::const_iterator i; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - SVDEBUG << "AudioCallbackPlaySource::rebuildRangeLists" << endl; -#endif - - if (!selections.empty()) { - - for (i = selections.begin(); i != selections.end(); ++i) { - - RealTime start = - (RealTime::frame2RealTime - (m_viewManager->alignReferenceToPlaybackFrame(i->getStartFrame()), - sourceRate)); - RealTime duration = - (RealTime::frame2RealTime - (m_viewManager->alignReferenceToPlaybackFrame(i->getEndFrame()) - - m_viewManager->alignReferenceToPlaybackFrame(i->getStartFrame()), - sourceRate)); - - m_rangeStarts.push_back(start); - m_rangeDurations.push_back(duration); - } - } else { - m_rangeStarts.push_back(RealTime::zeroTime); - m_rangeDurations.push_back(end); - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "Now have " << m_rangeStarts.size() << " play ranges" << endl; -#endif -} - -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) -{ - bool first = (m_targetSampleRate == 0); - - m_targetSampleRate = sr; - initialiseConverter(); - - if (first && (m_stretchRatio != 1.f)) { - // couldn't create a stretcher before because we had no sample - // rate: make one now - setTimeStretch(m_stretchRatio); - } -} - -void -AudioCallbackPlaySource::initialiseConverter() -{ - m_mutex.lock(); - - if (m_converter) { - src_delete(m_converter); - src_delete(m_crapConverter); - m_converter = 0; - m_crapConverter = 0; - } - - if (getSourceSampleRate() != getTargetSampleRate()) { - - int err = 0; - - m_converter = src_new(m_resampleQuality == 2 ? SRC_SINC_BEST_QUALITY : - m_resampleQuality == 1 ? SRC_SINC_MEDIUM_QUALITY : - m_resampleQuality == 0 ? SRC_SINC_FASTEST : - SRC_SINC_MEDIUM_QUALITY, - getTargetChannelCount(), &err); - - if (m_converter) { - m_crapConverter = src_new(SRC_LINEAR, - getTargetChannelCount(), - &err); - } - - if (!m_converter || !m_crapConverter) { - cerr - << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: " - << src_strerror(err) << endl; - - if (m_converter) { - src_delete(m_converter); - m_converter = 0; - } - - if (m_crapConverter) { - src_delete(m_crapConverter); - m_crapConverter = 0; - } - - m_mutex.unlock(); - - emit sampleRateMismatch(getSourceSampleRate(), - getTargetSampleRate(), - false); - } else { - - m_mutex.unlock(); - - emit sampleRateMismatch(getSourceSampleRate(), - getTargetSampleRate(), - true); - } - } else { - m_mutex.unlock(); - } -} - -void -AudioCallbackPlaySource::setResampleQuality(int q) -{ - if (q == m_resampleQuality) return; - m_resampleQuality = q; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - SVDEBUG << "AudioCallbackPlaySource::setResampleQuality: setting to " - << m_resampleQuality << endl; -#endif - - initialiseConverter(); -} - -void -AudioCallbackPlaySource::setAuditioningEffect(Auditionable *a) -{ - RealTimePluginInstance *plugin = dynamic_cast(a); - if (a && !plugin) { - cerr << "WARNING: AudioCallbackPlaySource::setAuditioningEffect: auditionable object " << a << " is not a real-time plugin instance" << endl; - } - - m_mutex.lock(); - m_auditioningPlugin = plugin; - m_auditioningPluginBypassed = false; - m_mutex.unlock(); -} - -void -AudioCallbackPlaySource::setSoloModelSet(std::set s) -{ - m_audioGenerator->setSoloModelSet(s); - clearRingBuffers(); -} - -void -AudioCallbackPlaySource::clearSoloModelSet() -{ - m_audioGenerator->clearSoloModelSet(); - clearRingBuffers(); -} - -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; -} - -void -AudioCallbackPlaySource::setTimeStretch(float factor) -{ - m_stretchRatio = factor; - - if (!getTargetSampleRate()) return; // have to make our stretcher later - - if (m_timeStretcher || (factor == 1.f)) { - // stretch ratio will be set in next process call if appropriate - } else { - m_stretcherInputCount = getTargetChannelCount(); - RubberBandStretcher *stretcher = new RubberBandStretcher - (getTargetSampleRate(), - m_stretcherInputCount, - RubberBandStretcher::OptionProcessRealTime, - factor); - RubberBandStretcher *monoStretcher = new RubberBandStretcher - (getTargetSampleRate(), - 1, - RubberBandStretcher::OptionProcessRealTime, - factor); - m_stretcherInputs = new float *[m_stretcherInputCount]; - m_stretcherInputSizes = new size_t[m_stretcherInputCount]; - for (size_t c = 0; c < m_stretcherInputCount; ++c) { - m_stretcherInputSizes[c] = 16384; - m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; - } - m_monoStretcher = monoStretcher; - m_timeStretcher = stretcher; - } - - emit activity(tr("Change time-stretch factor to %1").arg(factor)); -} - -size_t -AudioCallbackPlaySource::getSourceSamples(size_t ucount, float **buffer) -{ - int count = ucount; - - if (!m_playing) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Not playing" << endl; -#endif - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - for (int i = 0; i < count; ++i) { - buffer[ch][i] = 0.0; - } - } - return 0; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Playing" << endl; -#endif - - // Ensure that all buffers have at least the amount of data we - // need -- else reduce the size of our requests correspondingly - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - - RingBuffer *rb = getReadRingBuffer(ch); - - if (!rb) { - cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " - << "No ring buffer available for channel " << ch - << ", returning no data here" << endl; - count = 0; - break; - } - - size_t rs = rb->getReadSpace(); - if (rs < count) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " - << "Ring buffer for channel " << ch << " has only " - << rs << " (of " << count << ") samples available (" - << "ring buffer size is " << rb->getSize() << ", write " - << "space " << rb->getWriteSpace() << "), " - << "reducing request size" << endl; -#endif - count = rs; - } - } - - if (count == 0) return 0; - - RubberBandStretcher *ts = m_timeStretcher; - RubberBandStretcher *ms = m_monoStretcher; - - float ratio = ts ? ts->getTimeRatio() : 1.f; - - if (ratio != m_stretchRatio) { - if (!ts) { - cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: Time ratio change to " << m_stretchRatio << " is pending, but no stretcher is set" << endl; - m_stretchRatio = 1.f; - } else { - ts->setTimeRatio(m_stretchRatio); - if (ms) ms->setTimeRatio(m_stretchRatio); - if (m_stretchRatio >= 1.0) m_stretchMono = false; - } - } - - int stretchChannels = m_stretcherInputCount; - if (m_stretchMono) { - if (ms) { - ts = ms; - stretchChannels = 1; - } else { - m_stretchMono = false; - } - } - - if (m_target) { - m_lastRetrievedBlockSize = count; - m_lastRetrievalTimestamp = m_target->getCurrentTime(); - } - - if (!ts || ratio == 1.f) { - - int got = 0; - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - - RingBuffer *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 - cout << "AudioCallbackPlaySource::getSamples: got " << got << " (of " << count << ") samples on channel " << ch << ", signalling for more (possibly)" << endl; -#endif - } - - for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { - for (int i = got; i < count; ++i) { - buffer[ch][i] = 0.0; - } - } - } - - applyAuditioningEffect(count, buffer); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::getSamples: awakening thread" << endl; -#endif - - m_condition.wakeAll(); - - return got; - } - - size_t channels = getTargetChannelCount(); - size_t available; - int warned = 0; - size_t fedToStretcher = 0; - - // The input block for a given output is approx output / ratio, - // but we can't predict it exactly, for an adaptive timestretcher. - - while ((available = ts->available()) < count) { - - size_t reqd = lrintf((count - available) / ratio); - reqd = std::max(reqd, ts->getSamplesRequired()); - if (reqd == 0) reqd = 1; - - size_t got = reqd; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "reqd = " <= m_stretcherInputCount) continue; - RingBuffer *rb = getReadRingBuffer(c); - if (rb) { - size_t gotHere; - if (stretchChannels == 1 && c > 0) { - gotHere = rb->readAdding(m_stretcherInputs[0], got); - } else { - gotHere = rb->read(m_stretcherInputs[c], got); - } - if (gotHere < got) got = gotHere; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - if (c == 0) { - SVDEBUG << "feeding stretcher: got " << gotHere - << ", " << rb->getReadSpace() << " remain" << endl; - } -#endif - - } else { - cerr << "WARNING: No ring buffer available for channel " << c << " in stretcher input block" << endl; - } - } - - if (got < reqd) { - cerr << "WARNING: Read underrun in playback (" - << got << " < " << reqd << ")" << endl; - } - - ts->process(m_stretcherInputs, got, false); - - fedToStretcher += got; - - if (got == 0) break; - - if (ts->available() == available) { - cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << endl; - if (++warned == 5) break; - } - } - - ts->retrieve(buffer, count); - - for (int c = stretchChannels; c < getTargetChannelCount(); ++c) { - for (int i = 0; i < count; ++i) { - buffer[c][i] = buffer[0][i]; - } - } - - applyAuditioningEffect(count, buffer); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::getSamples [stretched]: awakening thread" << endl; -#endif - - m_condition.wakeAll(); - - return count; -} - -void -AudioCallbackPlaySource::applyAuditioningEffect(size_t count, float **buffers) -{ - if (m_auditioningPluginBypassed) return; - RealTimePluginInstance *plugin = m_auditioningPlugin; - if (!plugin) return; - - if (plugin->getAudioInputCount() != getTargetChannelCount()) { -// cerr << "plugin input count " << plugin->getAudioInputCount() -// << " != our channel count " << getTargetChannelCount() -// << endl; - return; - } - if (plugin->getAudioOutputCount() != getTargetChannelCount()) { -// cerr << "plugin output count " << plugin->getAudioOutputCount() -// << " != our channel count " << getTargetChannelCount() -// << endl; - return; - } - if (plugin->getBufferSize() < count) { -// cerr << "plugin buffer size " << plugin->getBufferSize() -// << " < our block size " << count -// << endl; - return; - } - - float **ib = plugin->getAudioInputBuffers(); - float **ob = plugin->getAudioOutputBuffers(); - - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - for (size_t i = 0; i < count; ++i) { - ib[c][i] = buffers[c][i]; - } - } - - plugin->run(Vamp::RealTime::zeroTime, count); - - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - for (size_t i = 0; i < count; ++i) { - buffers[c][i] = ob[c][i]; - } - } -} - -// 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 *wb = getWriteRingBuffer(c); - if (wb) { - size_t spaceHere = wb->getWriteSpace(); - if (c == 0 || spaceHere < space) space = spaceHere; - } - } - - if (space == 0) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySourceFillThread: no space to fill" << endl; -#endif - return false; - } - - size_t f = m_writeBufferFill; - - bool readWriteEqual = (m_readBuffers == m_writeBuffers); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - if (!readWriteEqual) { - cout << "AudioCallbackPlaySourceFillThread: note read buffers != write buffers" << endl; - } - cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << endl; -#endif - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "buffered to " << f << " already" << endl; -#endif - - bool resample = (getSourceSampleRate() != getTargetSampleRate()); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << 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) { - cerr << "WARNING: sample rates differ, but no converter available!" << 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); // also modifies f - - // 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 = 0; - - if (m_timeStretcher && m_timeStretcher->getTimeRatio() < 0.4) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Using crappy converter" << endl; -#endif - err = src_process(m_crapConverter, &data); - } else { - err = src_process(m_converter, &data); - } - - size_t toCopy = size_t(got * ratio + 0.1); - - if (err) { - cerr - << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: " - << src_strerror(err) << endl; - //!!! Then what? - } else { - got = data.input_frames_used; - toCopy = data.output_frames_gen; -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Resampled " << got << " frames to " << toCopy << " frames" << 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 *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 - size_t reqSpace = space; - space = (reqSpace / generatorBlockSize) * generatorBlockSize; - if (space == 0) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "requested fill of " << reqSpace - << " is less than generator block size of " - << generatorBlockSize << ", leaving it" << endl; -#endif - 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); // also modifies f - - for (size_t c = 0; c < channels; ++c) { - - RingBuffer *wb = getWriteRingBuffer(c); - if (wb) { - size_t actual = wb->write(bufferPtrs[c], got); -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Wrote " << actual << " samples for ch " << c << ", now " - << wb->getReadSpace() << " to read" - << endl; -#endif - if (actual < got) { - cerr << "WARNING: Buffer overrun in channel " << c - << ": wrote " << actual << " of " << got - << " samples" << endl; - } - } - } - - m_writeBufferFill = f; - if (readWriteEqual) m_readBufferFill = f; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Read buffer fill is now " << m_readBufferFill << endl; -#endif - - //!!! 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 - cout << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << 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) { - - size_t rChunkStart = - m_viewManager->alignPlaybackFrameToReference(chunkStart); - - Selection selection = - m_viewManager->getContainingSelection(rChunkStart, true); - - if (selection.isEmpty()) { - if (looping) { - selection = *m_viewManager->getSelections().begin(); - chunkStart = m_viewManager->alignReferenceToPlaybackFrame - (selection.getStartFrame()); - fadeIn = 50; - } - } - - if (selection.isEmpty()) { - - chunkSize = 0; - nextChunkStart = chunkStart; - - } else { - - size_t sf = m_viewManager->alignReferenceToPlaybackFrame - (selection.getStartFrame()); - size_t ef = m_viewManager->alignReferenceToPlaybackFrame - (selection.getEndFrame()); - - selectionSize = ef - sf; - - if (chunkStart < sf) { - chunkStart = sf; - fadeIn = 50; - } - - nextChunkStart = chunkStart + chunkSize; - - if (nextChunkStart >= ef) { - nextChunkStart = ef; - 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; - } - -// cout << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << endl; - - if (!chunkSize) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Ending selection playback at " << nextChunkStart << 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 - cout << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << 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::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 - cout << "Returning selection playback " << processed << " frames to " << nextChunkStart << 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 *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 -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - SVDEBUG << "AudioCallbackPlaySource::unifyRingBuffers: Not unifying: write buffer has less (" << wb->getReadSpace() << ") than " << m_blockSize*2 << " to read and write buffer fill (" << m_writeBufferFill << ") is not close to end frame (" << m_lastModelEndFrame << ")" << endl; -#endif - return; - } - } - break; - } - } - - size_t rf = m_readBufferFill; - RingBuffer *rb = getReadRingBuffer(0); - if (rb) { - size_t rs = rb->getReadSpace(); - //!!! incorrect when in non-contiguous selection, see comments elsewhere -// cout << "rs = " << rs << endl; - if (rs < rf) rf -= rs; - else rf = 0; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - SVDEBUG << "AudioCallbackPlaySource::unifyRingBuffers: m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << endl; -#endif - - size_t wf = m_writeBufferFill; - size_t skip = 0; - for (size_t c = 0; c < getTargetChannelCount(); ++c) { - RingBuffer *wb = getWriteRingBuffer(c); - if (wb) { - if (c == 0) { - - size_t wrs = wb->getReadSpace(); -// cout << "wrs = " << wrs << endl; - - if (wrs < wf) wf -= wrs; - else wf = 0; -// cout << "wf = " << wf << endl; - - if (wf < rf) skip = rf - wf; - if (skip == 0) break; - } - -// cout << "skipping " << skip << endl; - wb->skip(skip); - } - } - - m_bufferScavenger.claim(m_readBuffers); - m_readBuffers = m_writeBuffers; - m_readBufferFill = m_writeBufferFill; -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "unified" << endl; -#endif -} - -void -AudioCallbackPlaySource::FillThread::run() -{ - AudioCallbackPlaySource &s(m_source); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySourceFillThread starting" << 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_pluginScavenger.scavenge(); - - if (work && s.m_playing && s.getSourceSampleRate()) { - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySourceFillThread: not waiting" << endl; -#endif - - s.m_mutex.unlock(); - s.m_mutex.lock(); - - } else { - - float ms = 100; - if (s.getSourceSampleRate() > 0) { - ms = float(s.m_ringBufferSize) / - float(s.getSourceSampleRate()) * 1000.0; - } - - if (s.m_playing) ms /= 10; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - if (!s.m_playing) cout << endl; - cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms << "ms..." << endl; -#endif - - s.m_condition.wait(&s.m_mutex, size_t(ms)); - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySourceFillThread: awoken" << endl; -#endif - - work = false; - - if (!s.getSourceSampleRate()) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySourceFillThread: source sample rate is zero" << endl; -#endif - continue; - } - - bool playing = s.m_playing; - - if (playing && !previouslyPlaying) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << endl; -#endif - for (size_t c = 0; c < s.getTargetChannelCount(); ++c) { - RingBuffer *rb = s.getReadRingBuffer(c); - if (rb) rb->reset(); - } - } - previouslyPlaying = playing; - - work = s.fillBuffers(); - } - - s.m_mutex.unlock(); -} - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioCallbackPlaySource.h --- a/audioio/AudioCallbackPlaySource.h Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,382 +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 and QMUL. - - 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/PropertyContainer.h" -#include "base/Scavenger.h" - -#include -#include -#include - -#include "base/Thread.h" -#include "base/RealTime.h" - -#include - -#include -#include - -namespace RubberBand { - class RubberBandStretcher; -} - -class Model; -class ViewManagerBase; -class AudioGenerator; -class PlayParameters; -class RealTimePluginInstance; -class AudioCallbackPlayTarget; - -/** - * 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 QObject, - public AudioPlaySource -{ - Q_OBJECT - -public: - AudioCallbackPlaySource(ViewManagerBase *, QString clientName); - 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(); - - /** - * Return the last frame that would come out of the speakers if we - * stopped playback right now. - */ - virtual size_t getCurrentBufferedFrame(); - - /** - * Return the frame at which playback is expected to end (if not looping). - */ - virtual size_t getPlayEndFrame() { return m_lastModelEndFrame; } - - /** - * Set the target and the block size of the target audio device. - * This should be called by the target class. - */ - void setTarget(AudioCallbackPlayTarget *, size_t blockSize); - - /** - * Get the block size of the target audio device. This may be an - * estimate or upper bound, if the target has a variable block - * size; the source should behave itself even if this value turns - * out to be inaccurate. - */ - 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. - */ - virtual 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); - - /** - * Set the time stretcher factor (i.e. playback speed). - */ - void setTimeStretch(float factor); - - /** - * Set the resampler quality, 0 - 2 where 0 is fastest and 2 is - * highest quality. - */ - void setResampleQuality(int q); - - /** - * Set a single real-time plugin as a processing effect for - * auditioning during playback. - * - * The plugin must have been initialised with - * getTargetChannelCount() channels and a getTargetBlockSize() - * sample frame processing block size. - * - * This playback source takes ownership of the plugin, which will - * be deleted at some point after the following call to - * setAuditioningEffect (depending on real-time constraints). - * - * Pass a null pointer to remove the current auditioning plugin, - * if any. - */ - void setAuditioningEffect(Auditionable *plugin); - - /** - * Specify that only the given set of models should be played. - */ - void setSoloModelSet(std::sets); - - /** - * Specify that all models should be played as normal (if not - * muted). - */ - void clearSoloModelSet(); - - QString getClientName() const { return m_clientName; } - -signals: - void modelReplaced(); - - void playStatusChanged(bool isPlaying); - - void sampleRateMismatch(size_t requested, size_t available, bool willResample); - - void audioOverloadPluginDisabled(); - void audioTimeStretchMultiChannelDisabled(); - - void activity(QString); - -public slots: - void audioProcessingOverload(); - -protected slots: - void selectionChanged(); - void playLoopModeChanged(); - void playSelectionModeChanged(); - void playParametersChanged(PlayParameters *); - void preferenceChanged(PropertyContainer::PropertyName); - void modelChanged(size_t startFrame, size_t endFrame); - -protected: - ViewManagerBase *m_viewManager; - AudioGenerator *m_audioGenerator; - QString m_clientName; - - class RingBufferVector : public std::vector *> { - public: - virtual ~RingBufferVector() { - while (!empty()) { - delete *begin(); - erase(begin()); - } - } - }; - - std::set m_models; - RingBufferVector *m_readBuffers; - RingBufferVector *m_writeBuffers; - size_t m_readBufferFill; - size_t m_writeBufferFill; - Scavenger m_bufferScavenger; - size_t m_sourceChannelCount; - size_t m_blockSize; - size_t m_sourceSampleRate; - size_t m_targetSampleRate; - size_t m_playLatency; - AudioCallbackPlayTarget *m_target; - double m_lastRetrievalTimestamp; - size_t m_lastRetrievedBlockSize; - bool m_trustworthyTimestamps; - size_t m_lastCurrentFrame; - bool m_playing; - bool m_exiting; - size_t m_lastModelEndFrame; - size_t m_ringBufferSize; - float m_outputLeft; - float m_outputRight; - RealTimePluginInstance *m_auditioningPlugin; - bool m_auditioningPluginBypassed; - Scavenger m_pluginScavenger; - size_t m_playStartFrame; - bool m_playStartFramePassed; - RealTime m_playStartedAt; - - RingBuffer *getWriteRingBuffer(size_t c) { - if (m_writeBuffers && c < m_writeBuffers->size()) { - return (*m_writeBuffers)[c]; - } else { - return 0; - } - } - - RingBuffer *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(); - - RubberBand::RubberBandStretcher *m_timeStretcher; - RubberBand::RubberBandStretcher *m_monoStretcher; - float m_stretchRatio; - bool m_stretchMono; - - size_t m_stretcherInputCount; - float **m_stretcherInputs; - size_t *m_stretcherInputSizes; - - // 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); - - // Called from getSourceSamples. - void applyAuditioningEffect(size_t count, float **buffers); - - // Ranges of current selections, if play selection is active - std::vector m_rangeStarts; - std::vector m_rangeDurations; - void rebuildRangeLists(); - - size_t getCurrentFrame(RealTime outputLatency); - - class FillThread : public Thread - { - public: - FillThread(AudioCallbackPlaySource &source) : - Thread(Thread::NonRTThread), - m_source(source) { } - - virtual void run(); - - protected: - AudioCallbackPlaySource &m_source; - }; - - QMutex m_mutex; - QWaitCondition m_condition; - FillThread *m_fillThread; - SRC_STATE *m_converter; - SRC_STATE *m_crapConverter; // for use when playing very fast - int m_resampleQuality; - void initialiseConverter(); -}; - -#endif - - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioCallbackPlayTarget.cpp --- a/audioio/AudioCallbackPlayTarget.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +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 - -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; -} - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioCallbackPlayTarget.h --- a/audioio/AudioCallbackPlayTarget.h Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +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 - -class AudioCallbackPlaySource; - -class AudioCallbackPlayTarget : public QObject -{ - Q_OBJECT - -public: - AudioCallbackPlayTarget(AudioCallbackPlaySource *source); - virtual ~AudioCallbackPlayTarget(); - - virtual bool isOK() const = 0; - - virtual void shutdown() = 0; - - virtual double getCurrentTime() 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 - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioCoreAudioTarget.cpp --- a/audioio/AudioCoreAudioTarget.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /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 diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioCoreAudioTarget.h --- a/audioio/AudioCoreAudioTarget.h Tue Jul 14 15:04:45 2015 +0100 +++ /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 -#include - -#include -#include -#include -#include -#include -#include - -#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 - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioGenerator.cpp --- a/audioio/AudioGenerator.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,713 +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 "data/model/NoteModel.h" -#include "data/model/DenseTimeValueModel.h" -#include "data/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 -#include - -#include -#include - -const size_t -AudioGenerator::m_pluginBlockSize = 2048; - -QString -AudioGenerator::m_sampleDir = ""; - -//#define DEBUG_AUDIO_GENERATOR 1 - -AudioGenerator::AudioGenerator() : - m_sourceSampleRate(0), - m_targetChannelCount(1), - m_soloing(false) -{ - initialiseSampleDir(); - - connect(PlayParameterRepository::getInstance(), - SIGNAL(playPluginIdChanged(const Playable *, QString)), - this, - SLOT(playPluginIdChanged(const Playable *, QString))); - - connect(PlayParameterRepository::getInstance(), - SIGNAL(playPluginConfigurationChanged(const Playable *, QString)), - this, - SLOT(playPluginConfigurationChanged(const Playable *, QString))); -} - -AudioGenerator::~AudioGenerator() -{ -#ifdef DEBUG_AUDIO_GENERATOR - SVDEBUG << "AudioGenerator::~AudioGenerator" << endl; -#endif -} - -void -AudioGenerator::initialiseSampleDir() -{ - if (m_sampleDir != "") return; - - try { - m_sampleDir = TempDirectory::getInstance()->getSubDirectoryPath("samples"); - } catch (DirectoryCreationFailed f) { - cerr << "WARNING: AudioGenerator::initialiseSampleDir:" - << " Failed to create temporary sample directory" - << 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)); - QString target = QDir(m_sampleDir).filePath(fileName); - - if (!file.copy(target)) { - cerr << "WARNING: AudioGenerator::getSampleDir: " - << "Unable to copy " << fileName - << " into temporary directory \"" - << m_sampleDir << "\"" << endl; - } else { - QFile tf(target); - tf.setPermissions(tf.permissions() | - QFile::WriteOwner | - QFile::WriteUser); - } - } -} - -bool -AudioGenerator::addModel(Model *model) -{ - if (m_sourceSampleRate == 0) { - - m_sourceSampleRate = model->getSampleRate(); - - } else { - - DenseTimeValueModel *dtvm = - dynamic_cast(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 Playable *playable, QString) -{ - const Model *model = dynamic_cast(playable); - if (!model) { - cerr << "WARNING: AudioGenerator::playPluginIdChanged: playable " - << playable << " is not a supported model type" - << endl; - return; - } - - 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 Playable *playable, - QString configurationXml) -{ -// SVDEBUG << "AudioGenerator::playPluginConfigurationChanged" << endl; - - const Model *model = dynamic_cast(playable); - if (!model) { - cerr << "WARNING: AudioGenerator::playPluginIdChanged: playable " - << playable << " is not a supported model type" - << endl; - return; - } - - if (m_synthMap.find(model) == m_synthMap.end()) { - SVDEBUG << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << endl; - return; - } - - RealTimePluginInstance *plugin = m_synthMap[model]; - if (plugin) { - PluginXml(plugin).setParametersFromXml(configurationXml); - } -} - -void -AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) -{ - if (m_sampleDir != "") { - plugin->configure("sampledir", m_sampleDir.toStdString()); - } -} - -RealTimePluginInstance * -AudioGenerator::loadPluginFor(const Model *model) -{ - QString pluginId, configurationXml; - - const Playable *playable = model; - if (!playable || !playable->canPlay()) return 0; - - PlayParameters *parameters = - PlayParameterRepository::getInstance()->getPlayParameters(playable); - if (parameters) { - pluginId = parameters->getPlayPluginId(); - configurationXml = parameters->getPlayPluginConfiguration(); - } - - if (pluginId == "") return 0; - - RealTimePluginInstance *plugin = loadPlugin(pluginId, ""); - if (!plugin) return 0; - - if (configurationXml != "") { - PluginXml(plugin).setParametersFromXml(configurationXml); - setSampleDir(plugin); - } - - configurationXml = PluginXml(plugin).toXmlString(); - - if (parameters) { - parameters->setPlayPluginId(pluginId); - parameters->setPlayPluginConfiguration(configurationXml); - } - - return plugin; -} - -RealTimePluginInstance * -AudioGenerator::loadPlugin(QString pluginId, QString program) -{ - RealTimePluginFactory *factory = - RealTimePluginFactory::instanceFor(pluginId); - - if (!factory) { - cerr << "Failed to get plugin factory" << endl; - return 0; - } - - RealTimePluginInstance *instance = - factory->instantiatePlugin - (pluginId, 0, 0, m_sourceSampleRate, m_pluginBlockSize, m_targetChannelCount); - - if (!instance) { - cerr << "Failed to instantiate plugin " << pluginId << 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 != "") { -// cerr << "first selecting default program " << defaultProgram << endl; - instance->selectProgram(defaultProgram); - } - if (program != "") { -// cerr << "now selecting desired program " << program << endl; - instance->selectProgram(program.toStdString()); - } - instance->setIdealChannelCount(m_targetChannelCount); // reset! - - return instance; -} - -void -AudioGenerator::removeModel(Model *model) -{ - SparseOneDimensionalModel *sodm = - dynamic_cast(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; - -// SVDEBUG << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << 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; -} - -void -AudioGenerator::setSoloModelSet(std::set s) -{ - QMutexLocker locker(&m_mutex); - - m_soloModelSet = s; - m_soloing = true; -} - -void -AudioGenerator::clearSoloModelSet() -{ - QMutexLocker locker(&m_mutex); - - m_soloModelSet.clear(); - m_soloing = false; -} - -size_t -AudioGenerator::mixModel(Model *model, size_t startFrame, size_t frameCount, - float **buffer, size_t fadeIn, size_t fadeOut) -{ - if (m_sourceSampleRate == 0) { - cerr << "WARNING: AudioGenerator::mixModel: No base source sample rate available" << endl; - return frameCount; - } - - QMutexLocker locker(&m_mutex); - - Playable *playable = model; - if (!playable || !playable->canPlay()) return frameCount; - - PlayParameters *parameters = - PlayParameterRepository::getInstance()->getPlayParameters(playable); - if (!parameters) return frameCount; - - bool playing = !parameters->isPlayMuted(); - if (!playing) { -#ifdef DEBUG_AUDIO_GENERATOR - cout << "AudioGenerator::mixModel(" << model << "): muted" << endl; -#endif - return frameCount; - } - - if (m_soloing) { - if (m_soloModelSet.find(model) == m_soloModelSet.end()) { -#ifdef DEBUG_AUDIO_GENERATOR - cout << "AudioGenerator::mixModel(" << model << "): not one of the solo'd models" << endl; -#endif - return frameCount; - } - } - - float gain = parameters->getPlayGain(); - float pan = parameters->getPlayPan(); - - DenseTimeValueModel *dtvm = dynamic_cast(model); - if (dtvm) { - return mixDenseTimeValueModel(dtvm, startFrame, frameCount, - buffer, gain, pan, fadeIn, fadeOut); - } - - bool synthetic = - (qobject_cast(model) || - qobject_cast(model)); - - if (synthetic) { - return mixSyntheticNoteModel(model, 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; - static size_t channelBufCount = 0; - - size_t totalFrames = frames + fadeIn/2 + fadeOut/2; - - size_t modelChannels = dtvm->getChannelCount(); - - if (channelBufSiz < totalFrames || channelBufCount < modelChannels) { - - for (size_t c = 0; c < channelBufCount; ++c) { - delete[] channelBuffer[c]; - } - - delete[] channelBuffer; - channelBuffer = new float *[modelChannels]; - - for (size_t c = 0; c < modelChannels; ++c) { - channelBuffer[c] = new float[totalFrames]; - } - - channelBufCount = modelChannels; - channelBufSiz = totalFrames; - } - - size_t got = 0; - - if (startFrame >= fadeIn/2) { - got = dtvm->getData(0, modelChannels - 1, - startFrame - fadeIn/2, - frames + fadeOut/2 + fadeIn/2, - channelBuffer); - } else { - size_t missing = fadeIn/2 - startFrame; - - for (size_t c = 0; c < modelChannels; ++c) { - channelBuffer[c] += missing; - } - - got = dtvm->getData(0, modelChannels - 1, - startFrame, - frames + fadeOut/2, - channelBuffer); - - for (size_t c = 0; c < modelChannels; ++c) { - channelBuffer[c] -= missing; - } - - got += missing; - } - - for (size_t c = 0; c < m_targetChannelCount; ++c) { - - size_t sourceChannel = (c % modelChannels); - -// SVDEBUG << "mixing channel " << c << " from source channel " << sourceChannel << 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; - } - } - - for (size_t i = 0; i < fadeIn/2; ++i) { - float *back = buffer[c]; - back -= fadeIn/2; - back[i] += (channelGain * channelBuffer[sourceChannel][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; - } - float val = channelBuffer[sourceChannel][i]; - if (i >= got) val = 0.f; - buffer[c][i] += mult * val; - } - } - - return got; -} - -size_t -AudioGenerator::mixSyntheticNoteModel(Model *model, - size_t startFrame, size_t frames, - float **buffer, float gain, float pan, - size_t /* fadeIn */, - size_t /* fadeOut */) -{ - RealTimePluginInstance *plugin = m_synthMap[model]; - 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 - cout << "mixModel [synthetic note]: frames " << frames - << ", blocks " << blocks << endl; -#endif - - snd_seq_event_t onEv; - onEv.type = SND_SEQ_EVENT_NOTEON; - onEv.data.note.channel = 0; - - 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[model]; - - for (size_t i = 0; i < blocks; ++i) { - - size_t reqStart = startFrame + i * m_pluginBlockSize; - - NoteList notes = getNotes(model, - reqStart + latency, - reqStart + latency + m_pluginBlockSize); - - Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime - (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); - - for (NoteList::const_iterator ni = notes.begin(); - ni != notes.end(); ++ni) { - - size_t noteFrame = ni->start; - - if (noteFrame >= latency) noteFrame -= latency; - - if (noteFrame < reqStart || - noteFrame >= reqStart + m_pluginBlockSize) continue; - - while (noteOffs.begin() != noteOffs.end() && - noteOffs.begin()->frame <= noteFrame) { - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (noteOffs.begin()->frame, m_sourceSampleRate); - - offEv.data.note.note = noteOffs.begin()->pitch; - -#ifdef DEBUG_AUDIO_GENERATOR - cerr << "mixModel [synthetic]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << endl; -#endif - - plugin->sendEvent(eventTime, &offEv); - noteOffs.erase(noteOffs.begin()); - } - - Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime - (noteFrame, m_sourceSampleRate); - - if (ni->isMidiPitchQuantized) { - onEv.data.note.note = ni->midiPitch; - } else { -#ifdef DEBUG_AUDIO_GENERATOR - cerr << "mixModel [synthetic]: non-pitch-quantized notes are not supported [yet], quantizing" << endl; -#endif - onEv.data.note.note = Pitch::getPitchForFrequency(ni->frequency); - } - - onEv.data.note.velocity = ni->velocity; - - plugin->sendEvent(eventTime, &onEv); - -#ifdef DEBUG_AUDIO_GENERATOR - cout << "mixModel [synthetic]: note at frame " << noteFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << endl; -#endif - - noteOffs.insert - (NoteOff(onEv.data.note.note, noteFrame + ni->duration)); - } - - 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 - cerr << "mixModel [synthetic]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << 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 - cout << "mixModel [synthetic]: adding " << m_pluginBlockSize << " samples from plugin output " << c << 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; -} - -AudioGenerator::NoteList -AudioGenerator::getNotes(Model *model, - size_t startFrame, - size_t endFrame) -{ - NoteList notes; - - SparseOneDimensionalModel *sodm = - qobject_cast(model); - - if (sodm) { - - SparseOneDimensionalModel::PointList points = - sodm->getPoints(startFrame, endFrame); - - for (SparseOneDimensionalModel::PointList::iterator pli = - points.begin(); pli != points.end(); ++pli) { - - notes.push_back - (NoteData(pli->frame, - m_sourceSampleRate / 6, // arbitrary short duration - 64, // default pitch - 100)); // default velocity - } - - return notes; - } - - NoteModel *nm = qobject_cast(model); - - if (nm) { - - NoteModel::PointList points = - nm->getPoints(startFrame, endFrame); - - for (NoteModel::PointList::iterator pli = - points.begin(); pli != points.end(); ++pli) { - - size_t duration = pli->duration; - if (duration == 0 || duration == 1) { - duration = m_sourceSampleRate / 20; - } - - int pitch = lrintf(pli->value); - - int velocity = 100; - if (pli->level > 0.f && pli->level <= 1.f) { - velocity = lrintf(pli->level * 127); - } - - NoteData note(pli->frame, - duration, - pitch, - velocity); - - if (nm->getScaleUnits() == "Hz") { - note.frequency = pli->value; - note.isMidiPitchQuantized = false; - } - - notes.push_back(note); - } - - return notes; - } - - return notes; -} - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioGenerator.h --- a/audioio/AudioGenerator.h Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +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; -class Playable; - -#include -#include - -#include -#include -#include - -class AudioGenerator : public QObject -{ - Q_OBJECT - -public: - AudioGenerator(); - virtual ~AudioGenerator(); - - /** - * Add a data model to be played from and initialise any necessary - * audio generation code. Returns true if the model will be - * played. 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); - - /** - * Specify that only the given set of models should be played. - */ - virtual void setSoloModelSet(std::sets); - - /** - * Specify that all models should be played as normal (if not - * muted). - */ - virtual void clearSoloModelSet(); - -protected slots: - void playPluginIdChanged(const Playable *, QString); - void playPluginConfigurationChanged(const Playable *, QString); - -protected: - size_t m_sourceSampleRate; - size_t m_targetChannelCount; - - bool m_soloing; - std::set m_soloModelSet; - - struct NoteData { - - NoteData(size_t _start, size_t _dur, int _mp, int _vel) : - start(_start), duration(_dur), midiPitch(_mp), frequency(0), - isMidiPitchQuantized(true), velocity(_vel) { }; - - size_t start; // audio sample frame - size_t duration; // in audio sample frames - int midiPitch; // 0-127 - int frequency; // Hz, to be used if isMidiPitchQuantized false - bool isMidiPitchQuantized; - int velocity; // MIDI-style 0-127 - }; - - typedef std::vector NoteList; - - struct NoteOff { - - NoteOff(int _p, size_t _f) : pitch(_p), frame(_f) { } - - int pitch; - size_t frame; - - struct Comparator { - bool operator()(const NoteOff &n1, const NoteOff &n2) const { - return n1.frame < n2.frame; - } - }; - }; - - typedef std::map PluginMap; - - typedef std::multiset NoteOffSet; - typedef std::map 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 void initialiseSampleDir(); - 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 mixSyntheticNoteModel - (Model *model, size_t startFrame, size_t frameCount, - float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); - - NoteList getNotes(Model *model, size_t startFrame, size_t endFrame); - - static const size_t m_pluginBlockSize; -}; - -#endif - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioJACKTarget.cpp --- a/audioio/AudioJACKTarget.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,471 +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 -#include - -#include - -//#define DEBUG_AUDIO_JACK_TARGET 1 - -#ifdef BUILD_STATIC -#ifdef Q_OS_LINUX - -// Some lunacy to enable JACK support in static builds. JACK isn't -// supposed to be linked statically, because it depends on a -// consistent shared memory layout between client library and daemon, -// so it's very fragile in the face of version mismatches. -// -// Therefore for static builds on Linux we avoid linking against JACK -// at all during the build, instead using dlopen and runtime symbol -// lookup to switch on JACK support at runtime. The following big -// mess (down to the #endifs) is the code that implements this. - -static void *symbol(const char *name) -{ - static bool attempted = false; - static void *library = 0; - static std::map symbols; - if (symbols.find(name) != symbols.end()) return symbols[name]; - if (!library) { - if (!attempted) { - library = ::dlopen("libjack.so.1", RTLD_NOW); - if (!library) library = ::dlopen("libjack.so.0", RTLD_NOW); - if (!library) library = ::dlopen("libjack.so", RTLD_NOW); - if (!library) { - cerr << "WARNING: AudioJACKTarget: Failed to load JACK library: " - << ::dlerror() << " (tried .so, .so.0, .so.1)" - << endl; - } - attempted = true; - } - if (!library) return 0; - } - void *symbol = ::dlsym(library, name); - if (!symbol) { - cerr << "WARNING: AudioJACKTarget: Failed to locate symbol " - << name << ": " << ::dlerror() << endl; - } - symbols[name] = symbol; - return symbol; -} - -static jack_client_t *dynamic_jack_client_open(const char *client_name, - jack_options_t options, - jack_status_t *status, ...) -{ - typedef jack_client_t *(*func)(const char *client_name, - jack_options_t options, - jack_status_t *status, ...); - void *s = symbol("jack_client_open"); - if (!s) return 0; - func f = (func)s; - return f(client_name, options, status); // varargs not supported here -} - -static int dynamic_jack_set_process_callback(jack_client_t *client, - JackProcessCallback process_callback, - void *arg) -{ - typedef int (*func)(jack_client_t *client, - JackProcessCallback process_callback, - void *arg); - void *s = symbol("jack_set_process_callback"); - if (!s) return 1; - func f = (func)s; - return f(client, process_callback, arg); -} - -static int dynamic_jack_set_xrun_callback(jack_client_t *client, - JackXRunCallback xrun_callback, - void *arg) -{ - typedef int (*func)(jack_client_t *client, - JackXRunCallback xrun_callback, - void *arg); - void *s = symbol("jack_set_xrun_callback"); - if (!s) return 1; - func f = (func)s; - return f(client, xrun_callback, arg); -} - -static const char **dynamic_jack_get_ports(jack_client_t *client, - const char *port_name_pattern, - const char *type_name_pattern, - unsigned long flags) -{ - typedef const char **(*func)(jack_client_t *client, - const char *port_name_pattern, - const char *type_name_pattern, - unsigned long flags); - void *s = symbol("jack_get_ports"); - if (!s) return 0; - func f = (func)s; - return f(client, port_name_pattern, type_name_pattern, flags); -} - -static jack_port_t *dynamic_jack_port_register(jack_client_t *client, - const char *port_name, - const char *port_type, - unsigned long flags, - unsigned long buffer_size) -{ - typedef jack_port_t *(*func)(jack_client_t *client, - const char *port_name, - const char *port_type, - unsigned long flags, - unsigned long buffer_size); - void *s = symbol("jack_port_register"); - if (!s) return 0; - func f = (func)s; - return f(client, port_name, port_type, flags, buffer_size); -} - -static int dynamic_jack_connect(jack_client_t *client, - const char *source, - const char *dest) -{ - typedef int (*func)(jack_client_t *client, - const char *source, - const char *dest); - void *s = symbol("jack_connect"); - if (!s) return 1; - func f = (func)s; - return f(client, source, dest); -} - -static void *dynamic_jack_port_get_buffer(jack_port_t *port, - jack_nframes_t sz) -{ - typedef void *(*func)(jack_port_t *, jack_nframes_t); - void *s = symbol("jack_port_get_buffer"); - if (!s) return 0; - func f = (func)s; - return f(port, sz); -} - -static int dynamic_jack_port_unregister(jack_client_t *client, - jack_port_t *port) -{ - typedef int(*func)(jack_client_t *, jack_port_t *); - void *s = symbol("jack_port_unregister"); - if (!s) return 0; - func f = (func)s; - return f(client, port); -} - -#define dynamic1(rv, name, argtype, failval) \ - static rv dynamic_##name(argtype arg) { \ - typedef rv (*func) (argtype); \ - void *s = symbol(#name); \ - if (!s) return failval; \ - func f = (func) s; \ - return f(arg); \ - } - -dynamic1(jack_client_t *, jack_client_new, const char *, 0); -dynamic1(jack_nframes_t, jack_get_buffer_size, jack_client_t *, 0); -dynamic1(jack_nframes_t, jack_get_sample_rate, jack_client_t *, 0); -dynamic1(int, jack_activate, jack_client_t *, 1); -dynamic1(int, jack_deactivate, jack_client_t *, 1); -dynamic1(int, jack_client_close, jack_client_t *, 1); -dynamic1(jack_nframes_t, jack_frame_time, jack_client_t *, 0); -dynamic1(jack_nframes_t, jack_port_get_latency, jack_port_t *, 0); -dynamic1(const char *, jack_port_name, const jack_port_t *, 0); - -#define jack_client_new dynamic_jack_client_new -#define jack_client_open dynamic_jack_client_open -#define jack_get_buffer_size dynamic_jack_get_buffer_size -#define jack_get_sample_rate dynamic_jack_get_sample_rate -#define jack_set_process_callback dynamic_jack_set_process_callback -#define jack_set_xrun_callback dynamic_jack_set_xrun_callback -#define jack_activate dynamic_jack_activate -#define jack_deactivate dynamic_jack_deactivate -#define jack_client_close dynamic_jack_client_close -#define jack_frame_time dynamic_jack_frame_time -#define jack_get_ports dynamic_jack_get_ports -#define jack_port_register dynamic_jack_port_register -#define jack_port_unregister dynamic_jack_port_unregister -#define jack_port_get_latency dynamic_jack_port_get_latency -#define jack_port_name dynamic_jack_port_name -#define jack_connect dynamic_jack_connect -#define jack_port_get_buffer dynamic_jack_port_get_buffer - -#endif -#endif - -AudioJACKTarget::AudioJACKTarget(AudioCallbackPlaySource *source) : - AudioCallbackPlayTarget(source), - m_client(0), - m_bufferSize(0), - m_sampleRate(0), - m_done(false) -{ - JackOptions options = JackNullOption; -#ifdef HAVE_PORTAUDIO_2_0 - options = JackNoStartServer; -#endif -#ifdef HAVE_LIBPULSE - options = JackNoStartServer; -#endif - - JackStatus status = JackStatus(0); - m_client = jack_client_open(source->getClientName().toLocal8Bit().data(), - options, &status); - - if (!m_client) { - cerr << "AudioJACKTarget: Failed to connect to JACK server: status code " - << status << endl; - return; - } - - m_bufferSize = jack_get_buffer_size(m_client); - m_sampleRate = jack_get_sample_rate(m_client); - - jack_set_xrun_callback(m_client, xrunStatic, this); - jack_set_process_callback(m_client, processStatic, this); - - if (jack_activate(m_client)) { - cerr << "ERROR: AudioJACKTarget: Failed to activate JACK client" - << endl; - } - - if (m_source) { - sourceModelReplaced(); - } - - // Mainstream JACK (though not jackdmp) calls mlockall() to lock - // down all memory for real-time operation. That isn't a terribly - // good idea in an application like this that may have very high - // dynamic memory usage in other threads, as mlockall() applies - // across all threads. We're far better off undoing it here and - // accepting the possible loss of true RT capability. - MUNLOCKALL(); -} - -AudioJACKTarget::~AudioJACKTarget() -{ - SVDEBUG << "AudioJACKTarget::~AudioJACKTarget()" << endl; - - if (m_source) { - m_source->setTarget(0, m_bufferSize); - } - - shutdown(); - - if (m_client) { - - while (m_outputs.size() > 0) { - std::vector::iterator itr = m_outputs.end(); - --itr; - jack_port_t *port = *itr; - cerr << "unregister " << m_outputs.size() << endl; - if (port) jack_port_unregister(m_client, port); - m_outputs.erase(itr); - } - cerr << "Deactivating... "; - jack_deactivate(m_client); - cerr << "done\nClosing... "; - jack_client_close(m_client); - cerr << "done" << endl; - } - - m_client = 0; - - SVDEBUG << "AudioJACKTarget::~AudioJACKTarget() done" << endl; -} - -void -AudioJACKTarget::shutdown() -{ - m_done = true; -} - -bool -AudioJACKTarget::isOK() const -{ - return (m_client != 0); -} - -double -AudioJACKTarget::getCurrentTime() const -{ - if (m_client && m_sampleRate) { - return double(jack_frame_time(m_client)) / double(m_sampleRate); - } else { - return 0.0; - } -} - -int -AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg) -{ - return ((AudioJACKTarget *)arg)->process(nframes); -} - -int -AudioJACKTarget::xrunStatic(void *arg) -{ - return ((AudioJACKTarget *)arg)->xrun(); -} - -void -AudioJACKTarget::sourceModelReplaced() -{ - m_mutex.lock(); - - m_source->setTarget(this, 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 - SVDEBUG << "AudioJACKTarget::sourceModelReplaced: have " << channels << " channels and " << physicalPortCount << " physical ports" << endl; -#endif - - while (m_outputs.size() < channels) { - - char name[20]; - jack_port_t *port; - - sprintf(name, "out %d", int(m_outputs.size() + 1)); - - port = jack_port_register(m_client, - name, - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, - 0); - - if (!port) { - cerr - << "ERROR: AudioJACKTarget: Failed to create JACK output port " - << m_outputs.size() << 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::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_done) return 0; - - if (!m_mutex.tryLock()) { - return 0; - } - - if (m_outputs.empty()) { - m_mutex.unlock(); - return 0; - } - -#ifdef DEBUG_AUDIO_JACK_TARGET - cout << "AudioJACKTarget::process(" << nframes << "): have a source" << endl; -#endif - -#ifdef DEBUG_AUDIO_JACK_TARGET - if (m_bufferSize != nframes) { - cerr << "WARNING: m_bufferSize != nframes (" << m_bufferSize << " != " << nframes << ")" << 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); - } - - size_t received = 0; - - if (m_source) { - received = m_source->getSourceSamples(nframes, buffers); - } - - for (size_t ch = 0; ch < m_outputs.size(); ++ch) { - for (size_t i = received; 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; -} - -int -AudioJACKTarget::xrun() -{ - cerr << "AudioJACKTarget: xrun!" << endl; - if (m_source) m_source->audioProcessingOverload(); - return 0; -} - -#endif /* HAVE_JACK */ - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioJACKTarget.h --- a/audioio/AudioJACKTarget.h Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +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 -#include - -#include "AudioCallbackPlayTarget.h" - -#include - -class AudioCallbackPlaySource; - -class AudioJACKTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioJACKTarget(AudioCallbackPlaySource *source); - virtual ~AudioJACKTarget(); - - virtual void shutdown(); - - virtual bool isOK() const; - - virtual double getCurrentTime() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - int process(jack_nframes_t nframes); - int xrun(); - - static int processStatic(jack_nframes_t, void *); - static int xrunStatic(void *); - - jack_client_t *m_client; - std::vector m_outputs; - jack_nframes_t m_bufferSize; - jack_nframes_t m_sampleRate; - QMutex m_mutex; - bool m_done; -}; - -#endif /* HAVE_JACK */ - -#endif - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioPortAudioTarget.cpp --- a/audioio/AudioPortAudioTarget.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,300 +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_2_0 - -#include "AudioPortAudioTarget.h" -#include "AudioCallbackPlaySource.h" - -#include -#include -#include - -#ifndef _WIN32 -#include -#endif - -//#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), - m_prioritySet(false), - m_done(false) -{ - PaError err; - -#ifdef DEBUG_AUDIO_PORT_AUDIO_TARGET - cerr << "AudioPortAudioTarget: Initialising for PortAudio v19" << endl; -#endif - - err = Pa_Initialize(); - if (err != paNoError) { - cerr << "ERROR: AudioPortAudioTarget: Failed to initialize PortAudio: " << Pa_GetErrorText(err) << endl; - return; - } - - m_bufferSize = 2048; - m_sampleRate = 44100; - if (m_source && (m_source->getSourceSampleRate() != 0)) { - m_sampleRate = m_source->getSourceSampleRate(); - } - - PaStreamParameters op; - op.device = Pa_GetDefaultOutputDevice(); - op.channelCount = 2; - op.sampleFormat = paFloat32; - op.suggestedLatency = 0.2; - op.hostApiSpecificStreamInfo = 0; - err = Pa_OpenStream(&m_stream, 0, &op, m_sampleRate, - paFramesPerBufferUnspecified, - paNoFlag, processStatic, this); - - if (err != paNoError) { - - cerr << "WARNING: AudioPortAudioTarget: Failed to open PortAudio stream with default frames per buffer, trying again with fixed frames per buffer..." << endl; - - err = Pa_OpenStream(&m_stream, 0, &op, m_sampleRate, - 1024, - paNoFlag, processStatic, this); - m_bufferSize = 1024; - } - - if (err != paNoError) { - cerr << "ERROR: AudioPortAudioTarget: Failed to open PortAudio stream: " << Pa_GetErrorText(err) << endl; - cerr << "Note: device ID was " << op.device << endl; - m_stream = 0; - Pa_Terminate(); - return; - } - - const PaStreamInfo *info = Pa_GetStreamInfo(m_stream); - m_latency = int(info->outputLatency * m_sampleRate + 0.001); - if (m_bufferSize < m_latency) m_bufferSize = m_latency; - - cerr << "PortAudio latency = " << m_latency << " frames" << endl; - - err = Pa_StartStream(m_stream); - - if (err != paNoError) { - cerr << "ERROR: AudioPortAudioTarget: Failed to start PortAudio stream: " << Pa_GetErrorText(err) << endl; - Pa_CloseStream(m_stream); - m_stream = 0; - Pa_Terminate(); - return; - } - - if (m_source) { - cerr << "AudioPortAudioTarget: block size " << m_bufferSize << endl; - m_source->setTarget(this, m_bufferSize); - m_source->setTargetSampleRate(m_sampleRate); - m_source->setTargetPlayLatency(m_latency); - } - -#ifdef DEBUG_PORT_AUDIO_TARGET - cerr << "AudioPortAudioTarget: initialised OK" << endl; -#endif -} - -AudioPortAudioTarget::~AudioPortAudioTarget() -{ - SVDEBUG << "AudioPortAudioTarget::~AudioPortAudioTarget()" << endl; - - if (m_source) { - m_source->setTarget(0, m_bufferSize); - } - - shutdown(); - - if (m_stream) { - - SVDEBUG << "closing stream" << endl; - - PaError err; - err = Pa_CloseStream(m_stream); - if (err != paNoError) { - cerr << "ERROR: AudioPortAudioTarget: Failed to close PortAudio stream: " << Pa_GetErrorText(err) << endl; - } - - cerr << "terminating" << endl; - - err = Pa_Terminate(); - if (err != paNoError) { - cerr << "ERROR: AudioPortAudioTarget: Failed to terminate PortAudio: " << Pa_GetErrorText(err) << endl; - } - } - - m_stream = 0; - - SVDEBUG << "AudioPortAudioTarget::~AudioPortAudioTarget() done" << endl; -} - -void -AudioPortAudioTarget::shutdown() -{ -#ifdef DEBUG_PORT_AUDIO_TARGET - SVDEBUG << "AudioPortAudioTarget::shutdown" << endl; -#endif - m_done = true; -} - -bool -AudioPortAudioTarget::isOK() const -{ - return (m_stream != 0); -} - -double -AudioPortAudioTarget::getCurrentTime() const -{ - if (!m_stream) return 0.0; - else return Pa_GetStreamTime(m_stream); -} - -int -AudioPortAudioTarget::processStatic(const void *input, void *output, - unsigned long nframes, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags flags, void *data) -{ - return ((AudioPortAudioTarget *)data)->process(input, output, - nframes, timeInfo, - flags); -} - -void -AudioPortAudioTarget::sourceModelReplaced() -{ - m_source->setTargetSampleRate(m_sampleRate); -} - -int -AudioPortAudioTarget::process(const void *, void *outputBuffer, - unsigned long nframes, - const PaStreamCallbackTimeInfo *, - PaStreamCallbackFlags) -{ -#ifdef DEBUG_AUDIO_PORT_AUDIO_TARGET - SVDEBUG << "AudioPortAudioTarget::process(" << nframes << ")" << endl; -#endif - - if (!m_source || m_done) { -#ifdef DEBUG_AUDIO_PORT_AUDIO_TARGET - SVDEBUG << "AudioPortAudioTarget::process: Doing nothing, no source or application done" << endl; -#endif - return 0; - } - - if (!m_prioritySet) { -#ifndef _WIN32 - sched_param param; - param.sched_priority = 20; - if (pthread_setschedparam(pthread_self(), SCHED_RR, ¶m)) { - SVDEBUG << "AudioPortAudioTarget: NOTE: couldn't set RT scheduling class" << endl; - } else { - SVDEBUG << "AudioPortAudioTarget: NOTE: successfully set RT scheduling class" << endl; - } -#endif - m_prioritySet = true; - } - - 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 || int(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]; - } - } - - size_t received = 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) { - if (i < received) { - output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; - float sample = fabsf(output[i * 2 + ch]); - if (sample > peak) peak = sample; - } else { - output[i * 2 + ch] = 0; - } - } - - } else if (ch == 1 && sourceChannels == 1) { - - for (size_t i = 0; i < nframes; ++i) { - if (i < received) { - output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; - float sample = fabsf(output[i * 2 + ch]); - if (sample > peak) peak = sample; - } else { - output[i * 2 + ch] = 0; - } - } - - } 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); - - if (Pa_GetStreamCpuLoad(m_stream) > 0.7) { - if (m_source) m_source->audioProcessingOverload(); - } - - return 0; -} - -#endif /* HAVE_PORTAUDIO */ - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioPortAudioTarget.h --- a/audioio/AudioPortAudioTarget.h Tue Jul 14 15:04:45 2015 +0100 +++ /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. -*/ - -#ifndef _AUDIO_PORT_AUDIO_TARGET_H_ -#define _AUDIO_PORT_AUDIO_TARGET_H_ - -#ifdef HAVE_PORTAUDIO_2_0 - -// This code requires PortAudio v19 -- it won't work with v18. - -#include - -#include - -#include "AudioCallbackPlayTarget.h" - -class AudioCallbackPlaySource; - -class AudioPortAudioTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioPortAudioTarget(AudioCallbackPlaySource *source); - virtual ~AudioPortAudioTarget(); - - virtual void shutdown(); - - virtual bool isOK() const; - - virtual double getCurrentTime() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - int process(const void *input, void *output, unsigned long frames, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags); - - static int processStatic(const void *, void *, unsigned long, - const PaStreamCallbackTimeInfo *, - PaStreamCallbackFlags, void *); - - PaStream *m_stream; - - int m_bufferSize; - int m_sampleRate; - int m_latency; - bool m_prioritySet; - bool m_done; -}; - -#endif /* HAVE_PORTAUDIO */ - -#endif - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioPulseAudioTarget.cpp --- a/audioio/AudioPulseAudioTarget.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,433 +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 2008 QMUL. - - 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_LIBPULSE - -#include "AudioPulseAudioTarget.h" -#include "AudioCallbackPlaySource.h" - -#include - -#include -#include -#include - -#define DEBUG_AUDIO_PULSE_AUDIO_TARGET 1 -//#define DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY 1 - -AudioPulseAudioTarget::AudioPulseAudioTarget(AudioCallbackPlaySource *source) : - AudioCallbackPlayTarget(source), - m_mutex(QMutex::Recursive), - m_loop(0), - m_api(0), - m_context(0), - m_stream(0), - m_loopThread(0), - m_bufferSize(0), - m_sampleRate(0), - m_latency(0), - m_done(false) -{ -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET - cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << endl; -#endif - - m_loop = pa_mainloop_new(); - if (!m_loop) { - cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << endl; - return; - } - - m_api = pa_mainloop_get_api(m_loop); - - //!!! handle signals how? - - m_bufferSize = 20480; - m_sampleRate = 44100; - if (m_source && (m_source->getSourceSampleRate() != 0)) { - m_sampleRate = m_source->getSourceSampleRate(); - } - m_spec.rate = m_sampleRate; - m_spec.channels = 2; - m_spec.format = PA_SAMPLE_FLOAT32NE; - -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET - cerr << "AudioPulseAudioTarget: Creating context" << endl; -#endif - - m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data()); - if (!m_context) { - cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << endl; - return; - } - - pa_context_set_state_callback(m_context, contextStateChangedStatic, this); - -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET - cerr << "AudioPulseAudioTarget: Connecting to default server..." << endl; -#endif - - pa_context_connect(m_context, 0, // default server - (pa_context_flags_t)PA_CONTEXT_NOAUTOSPAWN, 0); - -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET - cerr << "AudioPulseAudioTarget: Starting main loop" << endl; -#endif - - m_loopThread = new MainLoopThread(m_loop); - m_loopThread->start(); - -#ifdef DEBUG_PULSE_AUDIO_TARGET - cerr << "AudioPulseAudioTarget: initialised OK" << endl; -#endif -} - -AudioPulseAudioTarget::~AudioPulseAudioTarget() -{ - SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << endl; - - if (m_source) { - m_source->setTarget(0, m_bufferSize); - } - - shutdown(); - - QMutexLocker locker(&m_mutex); - - if (m_stream) pa_stream_unref(m_stream); - - if (m_context) pa_context_unref(m_context); - - if (m_loop) { - pa_signal_done(); - pa_mainloop_free(m_loop); - } - - m_stream = 0; - m_context = 0; - m_loop = 0; - - SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << endl; -} - -void -AudioPulseAudioTarget::shutdown() -{ - m_done = true; -} - -bool -AudioPulseAudioTarget::isOK() const -{ - return (m_context != 0); -} - -double -AudioPulseAudioTarget::getCurrentTime() const -{ - if (!m_stream) return 0.0; - - pa_usec_t usec = 0; - pa_stream_get_time(m_stream, &usec); - return usec / 1000000.f; -} - -void -AudioPulseAudioTarget::sourceModelReplaced() -{ - m_source->setTargetSampleRate(m_sampleRate); -} - -void -AudioPulseAudioTarget::streamWriteStatic(pa_stream *stream, - size_t length, - void *data) -{ - AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; - - assert(stream == target->m_stream); - - target->streamWrite(length); -} - -void -AudioPulseAudioTarget::streamWrite(size_t requested) -{ -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY - cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << endl; -#endif - if (m_done) return; - - QMutexLocker locker(&m_mutex); - - pa_usec_t latency = 0; - int negative = 0; - if (!pa_stream_get_latency(m_stream, &latency, &negative)) { - int latframes = (latency / 1000000.f) * float(m_sampleRate); - if (latframes > 0) m_source->setTargetPlayLatency(latframes); - } - - static float *output = 0; - 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; - - size_t nframes = requested / (sourceChannels * sizeof(float)); - - if (nframes > m_bufferSize) { - cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << endl; - } - -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY - cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << endl; -#endif - - if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) { - - if (tmpbuf) { - for (size_t i = 0; i < tmpbufch; ++i) { - delete[] tmpbuf[i]; - } - delete[] tmpbuf; - } - - if (output) { - delete[] output; - } - - tmpbufch = sourceChannels; - tmpbufsz = nframes; - tmpbuf = new float *[tmpbufch]; - - for (size_t i = 0; i < tmpbufch; ++i) { - tmpbuf[i] = new float[tmpbufsz]; - } - - output = new float[tmpbufsz * tmpbufch]; - } - - size_t received = m_source->getSourceSamples(nframes, tmpbuf); - -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY - cerr << "requested " << nframes << ", received " << received << endl; - - if (received < nframes) { - cerr << "*** WARNING: Wrong number of frames received" << endl; - } -#endif - - float peakLeft = 0.0, peakRight = 0.0; - - for (size_t ch = 0; ch < 2; ++ch) { - - float peak = 0.0; - - if (ch < sourceChannels) { - - // PulseAudio samples are interleaved - for (size_t i = 0; i < nframes; ++i) { - if (i < received) { - output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; - float sample = fabsf(output[i * 2 + ch]); - if (sample > peak) peak = sample; - } else { - output[i * 2 + ch] = 0; - } - } - - } else if (ch == 1 && sourceChannels == 1) { - - for (size_t i = 0; i < nframes; ++i) { - if (i < received) { - output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; - float sample = fabsf(output[i * 2 + ch]); - if (sample > peak) peak = sample; - } else { - output[i * 2 + ch] = 0; - } - } - - } 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; - } - -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY - SVDEBUG << "calling pa_stream_write with " - << nframes * tmpbufch * sizeof(float) << " bytes" << endl; -#endif - - pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float), - 0, 0, PA_SEEK_RELATIVE); - - m_source->setOutputLevels(peakLeft, peakRight); - - return; -} - -void -AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream, - void *data) -{ - AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; - - assert(stream == target->m_stream); - - target->streamStateChanged(); -} - -void -AudioPulseAudioTarget::streamStateChanged() -{ -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET - SVDEBUG << "AudioPulseAudioTarget::streamStateChanged" << endl; -#endif - QMutexLocker locker(&m_mutex); - - switch (pa_stream_get_state(m_stream)) { - - case PA_STREAM_CREATING: - case PA_STREAM_TERMINATED: - break; - - case PA_STREAM_READY: - { - SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl; - - pa_usec_t latency = 0; - int negative = 0; - if (pa_stream_get_latency(m_stream, &latency, &negative)) { - cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << endl; - } - cerr << "Latency = " << latency << " usec" << endl; - int latframes = (latency / 1000000.f) * float(m_sampleRate); - cerr << "that's " << latframes << " frames" << endl; - - const pa_buffer_attr *attr; - if (!(attr = pa_stream_get_buffer_attr(m_stream))) { - SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl; - m_source->setTarget(this, m_bufferSize); - m_source->setTargetSampleRate(m_sampleRate); - if (latframes != 0) m_source->setTargetPlayLatency(latframes); - } else { - int targetLength = attr->tlength; - SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl; - m_source->setTarget(this, targetLength); - m_source->setTargetSampleRate(m_sampleRate); - if (latframes == 0) latframes = targetLength; - cerr << "latency = " << latframes << endl; - m_source->setTargetPlayLatency(latframes); - } - } - break; - - case PA_STREAM_FAILED: - default: - cerr << "AudioPulseAudioTarget::streamStateChanged: Error: " - << pa_strerror(pa_context_errno(m_context)) << endl; - //!!! do something... - break; - } -} - -void -AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context, - void *data) -{ - AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; - - assert(context == target->m_context); - - target->contextStateChanged(); -} - -void -AudioPulseAudioTarget::contextStateChanged() -{ -#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET - SVDEBUG << "AudioPulseAudioTarget::contextStateChanged" << endl; -#endif - QMutexLocker locker(&m_mutex); - - switch (pa_context_get_state(m_context)) { - - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - - case PA_CONTEXT_READY: - SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Ready" - << endl; - - m_stream = pa_stream_new(m_context, "stream", &m_spec, 0); - assert(m_stream); //!!! - - pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this); - pa_stream_set_write_callback(m_stream, streamWriteStatic, this); - pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this); - pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this); - if (pa_stream_connect_playback - (m_stream, 0, 0, - pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING | - PA_STREAM_AUTO_TIMING_UPDATE), - 0, 0)) { //??? return value - cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << endl; - } - - break; - - case PA_CONTEXT_TERMINATED: - SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Terminated" << endl; - //!!! do something... - break; - - case PA_CONTEXT_FAILED: - default: - cerr << "AudioPulseAudioTarget::contextStateChanged: Error: " - << pa_strerror(pa_context_errno(m_context)) << endl; - //!!! do something... - break; - } -} - -void -AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *) -{ - SVDEBUG << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << endl; -} - -void -AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data) -{ - SVDEBUG << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << endl; - AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; - if (target && target->m_source) { - target->m_source->audioProcessingOverload(); - } -} - -#endif /* HAVE_PULSEAUDIO */ - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioPulseAudioTarget.h --- a/audioio/AudioPulseAudioTarget.h Tue Jul 14 15:04:45 2015 +0100 +++ /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 2008 QMUL. - - 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_PULSE_AUDIO_TARGET_H_ -#define _AUDIO_PULSE_AUDIO_TARGET_H_ - -#ifdef HAVE_LIBPULSE - -#include - -#include -#include -#include "base/Thread.h" - -#include "AudioCallbackPlayTarget.h" - -class AudioCallbackPlaySource; - -class AudioPulseAudioTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioPulseAudioTarget(AudioCallbackPlaySource *source); - virtual ~AudioPulseAudioTarget(); - - virtual void shutdown(); - - virtual bool isOK() const; - - virtual double getCurrentTime() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - void streamWrite(size_t); - void streamStateChanged(); - void contextStateChanged(); - - static void streamWriteStatic(pa_stream *, size_t, void *); - static void streamStateChangedStatic(pa_stream *, void *); - static void streamOverflowStatic(pa_stream *, void *); - static void streamUnderflowStatic(pa_stream *, void *); - static void contextStateChangedStatic(pa_context *, void *); - - QMutex m_mutex; - - class MainLoopThread : public Thread - { - public: - MainLoopThread(pa_mainloop *loop) : Thread(NonRTThread), m_loop(loop) { } //!!! or RTThread - virtual void run() { - int rv = 0; - pa_mainloop_run(m_loop, &rv); //!!! check return value from this, and rv - } - - private: - pa_mainloop *m_loop; - }; - - pa_mainloop *m_loop; - pa_mainloop_api *m_api; - pa_context *m_context; - pa_stream *m_stream; - pa_sample_spec m_spec; - - MainLoopThread *m_loopThread; - - int m_bufferSize; - int m_sampleRate; - int m_latency; - bool m_done; -}; - -#endif /* HAVE_PULSEAUDIO */ - -#endif - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioTargetFactory.cpp --- a/audioio/AudioTargetFactory.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +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 "AudioPulseAudioTarget.h" - -#include "AudioCallbackPlayTarget.h" - -#include - -#include - -AudioTargetFactory * -AudioTargetFactory::m_instance = 0; - -AudioTargetFactory * -AudioTargetFactory::getInstance() -{ - if (!m_instance) m_instance = new AudioTargetFactory(); - return m_instance; -} - -AudioTargetFactory::AudioTargetFactory() -{ -} - -std::vector -AudioTargetFactory::getCallbackTargetNames(bool includeAuto) const -{ - std::vector names; - if (includeAuto) names.push_back("auto"); - -#ifdef HAVE_JACK - names.push_back("jack"); -#endif - -#ifdef HAVE_LIBPULSE - names.push_back("pulse"); -#endif - -#ifdef HAVE_COREAUDIO - names.push_back("core"); -#endif - -#ifdef HAVE_PORTAUDIO_2_0 - names.push_back("port"); -#endif - - return names; -} - -QString -AudioTargetFactory::getCallbackTargetDescription(QString name) const -{ - if (name == "auto") { - return QCoreApplication::translate("AudioTargetFactory", - "(auto)"); - } - if (name == "jack") { - return QCoreApplication::translate("AudioTargetFactory", - "JACK Audio Connection Kit"); - } - if (name == "pulse") { - return QCoreApplication::translate("AudioTargetFactory", - "PulseAudio Server"); - } - if (name == "core") { - return QCoreApplication::translate("AudioTargetFactory", - "Core Audio Device"); - } - if (name == "port") { - return QCoreApplication::translate("AudioTargetFactory", - "Default Soundcard Device"); - } - - return "(unknown)"; -} - -QString -AudioTargetFactory::getDefaultCallbackTarget() const -{ - if (m_default == "") return "auto"; - return m_default; -} - -bool -AudioTargetFactory::isAutoCallbackTarget(QString name) const -{ - return (name == "auto" || name == ""); -} - -void -AudioTargetFactory::setDefaultCallbackTarget(QString target) -{ - m_default = target; -} - -AudioCallbackPlayTarget * -AudioTargetFactory::createCallbackTarget(AudioCallbackPlaySource *source) -{ - AudioCallbackPlayTarget *target = 0; - - if (m_default != "" && m_default != "auto") { - -#ifdef HAVE_JACK - if (m_default == "jack") target = new AudioJACKTarget(source); -#endif - -#ifdef HAVE_LIBPULSE - if (m_default == "pulse") target = new AudioPulseAudioTarget(source); -#endif - -#ifdef HAVE_COREAUDIO - if (m_default == "core") target = new AudioCoreAudioTarget(source); -#endif - -#ifdef HAVE_PORTAUDIO_2_0 - if (m_default == "port") target = new AudioPortAudioTarget(source); -#endif - - if (!target || !target->isOK()) { - cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open the requested target (\"" << m_default << "\")" << endl; - delete target; - return 0; - } else { - return target; - } - } - -#ifdef HAVE_JACK - target = new AudioJACKTarget(source); - if (target->isOK()) return target; - else { - cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open JACK target" << endl; - delete target; - } -#endif - -#ifdef HAVE_LIBPULSE - target = new AudioPulseAudioTarget(source); - if (target->isOK()) return target; - else { - cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open PulseAudio target" << endl; - delete target; - } -#endif - -#ifdef HAVE_COREAUDIO - target = new AudioCoreAudioTarget(source); - if (target->isOK()) return target; - else { - cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open CoreAudio target" << endl; - delete target; - } -#endif - -#ifdef HAVE_PORTAUDIO_2_0 - target = new AudioPortAudioTarget(source); - if (target->isOK()) return target; - else { - cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open PortAudio target" << endl; - delete target; - } -#endif - - cerr << "WARNING: AudioTargetFactory::createCallbackTarget: No suitable targets available" << endl; - return 0; -} - - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/AudioTargetFactory.h --- a/audioio/AudioTargetFactory.h Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +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_ - -#include -#include - -#include "base/Debug.h" - -class AudioCallbackPlaySource; -class AudioCallbackPlayTarget; - -class AudioTargetFactory -{ -public: - static AudioTargetFactory *getInstance(); - - std::vector getCallbackTargetNames(bool includeAuto = true) const; - QString getCallbackTargetDescription(QString name) const; - QString getDefaultCallbackTarget() const; - bool isAutoCallbackTarget(QString name) const; - void setDefaultCallbackTarget(QString name); - - AudioCallbackPlayTarget *createCallbackTarget(AudioCallbackPlaySource *); - -protected: - AudioTargetFactory(); - static AudioTargetFactory *m_instance; - QString m_default; -}; - -#endif - diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/PlaySpeedRangeMapper.cpp --- a/audioio/PlaySpeedRangeMapper.cpp Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +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 QMUL. - - 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 "PlaySpeedRangeMapper.h" - -#include -#include - -PlaySpeedRangeMapper::PlaySpeedRangeMapper(int minpos, int maxpos) : - m_minpos(minpos), - m_maxpos(maxpos) -{ -} - -int -PlaySpeedRangeMapper::getPositionForValue(float value) const -{ - // value is percent - float factor = getFactorForValue(value); - int position = getPositionForFactor(factor); - return position; -} - -int -PlaySpeedRangeMapper::getPositionForFactor(float factor) const -{ - bool slow = (factor > 1.0); - - if (!slow) factor = 1.0 / factor; - - int half = (m_maxpos + m_minpos) / 2; - - factor = sqrtf((factor - 1.0) * 1000.f); - int position = lrintf(((factor * (half - m_minpos)) / 100.0) + m_minpos); - - if (slow) { - position = half - position; - } else { - position = position + half; - } - -// cerr << "value = " << value << " slow = " << slow << " factor = " << factor << " position = " << position << endl; - - return position; -} - -float -PlaySpeedRangeMapper::getValueForPosition(int position) const -{ - float factor = getFactorForPosition(position); - float pc = getValueForFactor(factor); - return pc; -} - -float -PlaySpeedRangeMapper::getValueForFactor(float factor) const -{ - float pc; - if (factor < 1.0) pc = ((1.0 / factor) - 1.0) * 100.0; - else pc = (1.0 - factor) * 100.0; -// cerr << "position = " << position << " percent = " << pc << endl; - return pc; -} - -float -PlaySpeedRangeMapper::getFactorForValue(float value) const -{ - // value is percent - - float factor; - - if (value <= 0) { - factor = 1.0 - (value / 100.0); - } else { - factor = 1.0 / (1.0 + (value / 100.0)); - } - -// cerr << "value = " << value << " factor = " << factor << endl; - return factor; -} - -float -PlaySpeedRangeMapper::getFactorForPosition(int position) const -{ - bool slow = false; - - if (position < m_minpos) position = m_minpos; - if (position > m_maxpos) position = m_maxpos; - - int half = (m_maxpos + m_minpos) / 2; - - if (position < half) { - slow = true; - position = half - position; - } else { - position = position - half; - } - - // position is between min and half (inclusive) - - float factor; - - if (position == m_minpos) { - factor = 1.0; - } else { - factor = ((position - m_minpos) * 100.0) / (half - m_minpos); - factor = 1.0 + (factor * factor) / 1000.f; - } - - if (!slow) factor = 1.0 / factor; - -// cerr << "position = " << position << " slow = " << slow << " factor = " << factor << endl; - - return factor; -} - -QString -PlaySpeedRangeMapper::getUnit() const -{ - return "%"; -} diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/PlaySpeedRangeMapper.h --- a/audioio/PlaySpeedRangeMapper.h Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +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 QMUL. - - 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 _PLAY_SPEED_RANGE_MAPPER_H_ -#define _PLAY_SPEED_RANGE_MAPPER_H_ - -#include "base/RangeMapper.h" - -class PlaySpeedRangeMapper : public RangeMapper -{ -public: - PlaySpeedRangeMapper(int minpos, int maxpos); - - virtual int getPositionForValue(float value) const; - virtual float getValueForPosition(int position) const; - - int getPositionForFactor(float factor) const; - float getValueForFactor(float factor) const; - - float getFactorForPosition(int position) const; - float getFactorForValue(float value) const; - - virtual QString getUnit() const; - -protected: - int m_minpos; - int m_maxpos; -}; - - -#endif diff -r 428ce32a8dd9 -r a2a8fa0eed08 audioio/audioio.pro --- a/audioio/audioio.pro Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -TEMPLATE = lib - -SV_UNIT_PACKAGES = fftw3f samplerate jack portaudio-2.0 libpulse rubberband -load(../prf/sv.prf) - -CONFIG += sv staticlib qt thread warn_on stl rtti exceptions -QT -= gui - -TARGET = svaudioio - -DEPENDPATH += .. -INCLUDEPATH += . .. -OBJECTS_DIR = tmp_obj -MOC_DIR = tmp_moc - -HEADERS += AudioCallbackPlaySource.h \ - AudioCallbackPlayTarget.h \ - AudioCoreAudioTarget.h \ - AudioGenerator.h \ - AudioJACKTarget.h \ - AudioPortAudioTarget.h \ - AudioPulseAudioTarget.h \ - AudioTargetFactory.h \ - PlaySpeedRangeMapper.h -SOURCES += AudioCallbackPlaySource.cpp \ - AudioCallbackPlayTarget.cpp \ - AudioCoreAudioTarget.cpp \ - AudioGenerator.cpp \ - AudioJACKTarget.cpp \ - AudioPortAudioTarget.cpp \ - AudioPulseAudioTarget.cpp \ - AudioTargetFactory.cpp \ - PlaySpeedRangeMapper.cpp diff -r 428ce32a8dd9 -r a2a8fa0eed08 configure --- a/configure Tue Jul 14 15:04:45 2015 +0100 +++ b/configure Wed Apr 20 12:06:28 2016 +0100 @@ -646,16 +646,12 @@ libpulse_CFLAGS JACK_LIBS JACK_CFLAGS -portaudio_2_0_LIBS -portaudio_2_0_CFLAGS +portaudio_LIBS +portaudio_CFLAGS liblo_LIBS liblo_CFLAGS rubberband_LIBS rubberband_CFLAGS -vamphostsdk_LIBS -vamphostsdk_CFLAGS -vamp_LIBS -vamp_CFLAGS samplerate_LIBS samplerate_CFLAGS sndfile_LIBS @@ -673,6 +669,7 @@ EGREP GREP CXXCPP +HAVE_CXX11 MKDIR_P INSTALL_DATA INSTALL_SCRIPT @@ -755,16 +752,12 @@ sndfile_LIBS samplerate_CFLAGS samplerate_LIBS -vamp_CFLAGS -vamp_LIBS -vamphostsdk_CFLAGS -vamphostsdk_LIBS rubberband_CFLAGS rubberband_LIBS liblo_CFLAGS liblo_LIBS -portaudio_2_0_CFLAGS -portaudio_2_0_LIBS +portaudio_CFLAGS +portaudio_LIBS JACK_CFLAGS JACK_LIBS libpulse_CFLAGS @@ -1422,12 +1415,6 @@ C compiler flags for samplerate, overriding pkg-config samplerate_LIBS linker flags for samplerate, overriding pkg-config - vamp_CFLAGS C compiler flags for vamp, overriding pkg-config - vamp_LIBS linker flags for vamp, overriding pkg-config - vamphostsdk_CFLAGS - C compiler flags for vamphostsdk, overriding pkg-config - vamphostsdk_LIBS - linker flags for vamphostsdk, overriding pkg-config rubberband_CFLAGS C compiler flags for rubberband, overriding pkg-config rubberband_LIBS @@ -1435,10 +1422,10 @@ liblo_CFLAGS C compiler flags for liblo, overriding pkg-config liblo_LIBS linker flags for liblo, overriding pkg-config - portaudio_2_0_CFLAGS - C compiler flags for portaudio_2_0, overriding pkg-config - portaudio_2_0_LIBS - linker flags for portaudio_2_0, overriding pkg-config + portaudio_CFLAGS + C compiler flags for portaudio, overriding pkg-config + portaudio_LIBS + linker flags for portaudio, overriding pkg-config JACK_CFLAGS C compiler flags for JACK, overriding pkg-config JACK_LIBS linker flags for JACK, overriding pkg-config libpulse_CFLAGS @@ -3449,6 +3436,146 @@ $as_echo "$MKDIR_P" >&6; } +# We are daringly making use of C++11 now + + ax_cxx_compile_cxx11_required=true + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + ac_success=no + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features by default" >&5 +$as_echo_n "checking whether $CXX supports C++11 features by default... " >&6; } +if ${ax_cv_cxx_compile_cxx11+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ax_cv_cxx_compile_cxx11=yes +else + ax_cv_cxx_compile_cxx11=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx11" >&5 +$as_echo "$ax_cv_cxx_compile_cxx11" >&6; } + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + + + if test x$ac_success = xno; then + for switch in -std=c++11 -std=c++0x; do + cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5 +$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; } +if eval \${$cachevar+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + eval $cachevar=yes +else + eval $cachevar=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CXXFLAGS="$ac_save_CXXFLAGS" +fi +eval ac_res=\$$cachevar + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + as_fn_error $? "*** A compiler with support for C++11 language features is required." "$LINENO" 5 + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++11 support was found" >&5 +$as_echo "$as_me: No compiler with C++11 support was found" >&6;} + else + HAVE_CXX11=1 + +$as_echo "#define HAVE_CXX11 1" >>confdefs.h + + fi + + + fi + ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' @@ -3996,6 +4123,45 @@ fi if test x$QMAKE = x ; then + # Extract the first word of "qt5-qmake", so it can be a program name with args. +set dummy qt5-qmake; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_QMAKE+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$QMAKE"; then + ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $QTDIR/bin/ +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_QMAKE="$QTDIR/bin/qt5-qmake" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +QMAKE=$ac_cv_prog_QMAKE +if test -n "$QMAKE"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5 +$as_echo "$QMAKE" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test x$QMAKE = x ; then # Extract the first word of "qmake", so it can be a program name with args. set dummy qmake; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 @@ -4113,6 +4279,45 @@ fi if test x$QMAKE = x ; then + # Extract the first word of "qt5-qmake", so it can be a program name with args. +set dummy qt5-qmake; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_QMAKE+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$QMAKE"; then + ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_QMAKE="qt5-qmake" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +QMAKE=$ac_cv_prog_QMAKE +if test -n "$QMAKE"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5 +$as_echo "$QMAKE" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test x$QMAKE = x ; then # Extract the first word of "qmake", so it can be a program name with args. set dummy qmake; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 @@ -4202,9 +4407,10 @@ CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS" if test "x$GCC" = "xyes"; then - CXXFLAGS_DEBUG="-Wall -Woverloaded-virtual -Wextra -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -g -pipe" - CXXFLAGS_RELEASE="-g0 -O2 -Wall -pipe" - CXXFLAGS_MINIMAL="-g0 -O0" + CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe" + CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g" + CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2" + CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0" fi CXXFLAGS_BUILD="$CXXFLAGS_RELEASE" @@ -5006,18 +5212,18 @@ fi -SV_MODULE_MODULE=vamp -SV_MODULE_VERSION_TEST="vamp >= 2.1" -SV_MODULE_HEADER=vamp/vamp.h -SV_MODULE_LIB= -SV_MODULE_FUNC= -SV_MODULE_HAVE=HAVE_$(echo vamp | tr 'a-z' 'A-Z') +SV_MODULE_MODULE=rubberband +SV_MODULE_VERSION_TEST="rubberband" +SV_MODULE_HEADER=rubberband/RubberBandStretcher.h +SV_MODULE_LIB=rubberband +SV_MODULE_FUNC=rubberband_new +SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z') SV_MODULE_FAILED=1 -if test -n "$vamp_LIBS" ; then +if test -n "$rubberband_LIBS" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $vamp_CFLAGS" - LIBS="$LIBS $vamp_LIBS" + CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS" + LIBS="$LIBS $rubberband_LIBS" SV_MODULE_FAILED="" fi if test -z "$SV_MODULE_VERSION_TEST" ; then @@ -5026,11 +5232,11 @@ if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamp" >&5 -$as_echo_n "checking for vamp... " >&6; } - -if test -n "$vamp_CFLAGS"; then - pkg_cv_vamp_CFLAGS="$vamp_CFLAGS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5 +$as_echo_n "checking for rubberband... " >&6; } + +if test -n "$rubberband_CFLAGS"; then + pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5038,7 +5244,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamp_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5046,8 +5252,8 @@ else pkg_failed=untried fi -if test -n "$vamp_LIBS"; then - pkg_cv_vamp_LIBS="$vamp_LIBS" +if test -n "$rubberband_LIBS"; then + pkg_cv_rubberband_LIBS="$rubberband_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5055,7 +5261,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamp_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5076,12 +5282,12 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - vamp_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` else - vamp_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` fi # Put the nasty error message in config.log where it belongs - echo "$vamp_PKG_ERRORS" >&5 + echo "$rubberband_PKG_ERRORS" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} @@ -5091,11 +5297,11 @@ { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} else - vamp_CFLAGS=$pkg_cv_vamp_CFLAGS - vamp_LIBS=$pkg_cv_vamp_LIBS + rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS + rubberband_LIBS=$pkg_cv_rubberband_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamp_CFLAGS";LIBS="$LIBS $vamp_LIBS";SV_MODULE_FAILED="" + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED="" fi fi if test -n "$SV_MODULE_FAILED"; then @@ -5157,18 +5363,19 @@ fi -SV_MODULE_MODULE=vamphostsdk -SV_MODULE_VERSION_TEST="vamp-hostsdk >= 2.5" -SV_MODULE_HEADER=vamp-hostsdk/PluginLoader.h -SV_MODULE_LIB=vamp-hostsdk -SV_MODULE_FUNC=libvamphostsdk_v_2_5_present -SV_MODULE_HAVE=HAVE_$(echo vamphostsdk | tr 'a-z' 'A-Z') + +SV_MODULE_MODULE=liblo +SV_MODULE_VERSION_TEST="" +SV_MODULE_HEADER=lo/lo.h +SV_MODULE_LIB=lo +SV_MODULE_FUNC=lo_address_new +SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z') SV_MODULE_FAILED=1 -if test -n "$vamphostsdk_LIBS" ; then +if test -n "$liblo_LIBS" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS" - LIBS="$LIBS $vamphostsdk_LIBS" + CXXFLAGS="$CXXFLAGS $liblo_CFLAGS" + LIBS="$LIBS $liblo_LIBS" SV_MODULE_FAILED="" fi if test -z "$SV_MODULE_VERSION_TEST" ; then @@ -5177,11 +5384,11 @@ if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamphostsdk" >&5 -$as_echo_n "checking for vamphostsdk... " >&6; } - -if test -n "$vamphostsdk_CFLAGS"; then - pkg_cv_vamphostsdk_CFLAGS="$vamphostsdk_CFLAGS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5 +$as_echo_n "checking for liblo... " >&6; } + +if test -n "$liblo_CFLAGS"; then + pkg_cv_liblo_CFLAGS="$liblo_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5189,7 +5396,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamphostsdk_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5197,8 +5404,8 @@ else pkg_failed=untried fi -if test -n "$vamphostsdk_LIBS"; then - pkg_cv_vamphostsdk_LIBS="$vamphostsdk_LIBS" +if test -n "$liblo_LIBS"; then + pkg_cv_liblo_LIBS="$liblo_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5206,7 +5413,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamphostsdk_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5227,40 +5434,42 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` else - vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` fi # Put the nasty error message in config.log where it belongs - echo "$vamphostsdk_PKG_ERRORS" >&5 - - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} + echo "$liblo_PKG_ERRORS" >&5 + + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 +$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -else - vamphostsdk_CFLAGS=$pkg_cv_vamphostsdk_CFLAGS - vamphostsdk_LIBS=$pkg_cv_vamphostsdk_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 +$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} +else + liblo_CFLAGS=$pkg_cv_liblo_CFLAGS + liblo_LIBS=$pkg_cv_liblo_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS";LIBS="$LIBS $vamphostsdk_LIBS";SV_MODULE_FAILED="" + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED="" fi fi if test -n "$SV_MODULE_FAILED"; then as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - HAVES="$HAVES $SV_MODULE_HAVE" -else - as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - - - if test -n "$SV_MODULE_LIB"; then - as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` + HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED="" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;} +fi + + + if test -z "$SV_MODULE_FAILED"; then + if test -n "$SV_MODULE_LIB"; then + as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 $as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } if eval \${$as_ac_Lib+:} false; then : @@ -5301,25 +5510,27 @@ if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : LIBS="$LIBS -l$SV_MODULE_LIB" else - as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;} +fi + + fi fi fi -SV_MODULE_MODULE=rubberband -SV_MODULE_VERSION_TEST="rubberband" -SV_MODULE_HEADER=rubberband/RubberBandStretcher.h -SV_MODULE_LIB=rubberband -SV_MODULE_FUNC=rubberband_new -SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z') +SV_MODULE_MODULE=portaudio +SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19" +SV_MODULE_HEADER=portaudio.h +SV_MODULE_LIB=portaudio +SV_MODULE_FUNC=Pa_IsFormatSupported +SV_MODULE_HAVE=HAVE_$(echo portaudio | tr 'a-z' 'A-Z') SV_MODULE_FAILED=1 -if test -n "$rubberband_LIBS" ; then +if test -n "$portaudio_LIBS" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS" - LIBS="$LIBS $rubberband_LIBS" + CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS" + LIBS="$LIBS $portaudio_LIBS" SV_MODULE_FAILED="" fi if test -z "$SV_MODULE_VERSION_TEST" ; then @@ -5328,11 +5539,11 @@ if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5 -$as_echo_n "checking for rubberband... " >&6; } - -if test -n "$rubberband_CFLAGS"; then - pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio" >&5 +$as_echo_n "checking for portaudio... " >&6; } + +if test -n "$portaudio_CFLAGS"; then + pkg_cv_portaudio_CFLAGS="$portaudio_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5340,7 +5551,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_portaudio_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5348,8 +5559,8 @@ else pkg_failed=untried fi -if test -n "$rubberband_LIBS"; then - pkg_cv_rubberband_LIBS="$rubberband_LIBS" +if test -n "$portaudio_LIBS"; then + pkg_cv_portaudio_LIBS="$portaudio_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5357,7 +5568,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_portaudio_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5378,164 +5589,12 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + portaudio_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` else - rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + portaudio_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` fi # Put the nasty error message in config.log where it belongs - echo "$rubberband_PKG_ERRORS" >&5 - - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -else - rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS - rubberband_LIBS=$pkg_cv_rubberband_LIBS - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED="" -fi -fi -if test -n "$SV_MODULE_FAILED"; then - as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` -ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - HAVES="$HAVES $SV_MODULE_HAVE" -else - as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - - - if test -n "$SV_MODULE_LIB"; then - as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 -$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$SV_MODULE_LIB $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $SV_MODULE_FUNC (); -int -main () -{ -return $SV_MODULE_FUNC (); - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - LIBS="$LIBS -l$SV_MODULE_LIB" -else - as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - - fi -fi - - - -SV_MODULE_MODULE=liblo -SV_MODULE_VERSION_TEST="" -SV_MODULE_HEADER=lo/lo.h -SV_MODULE_LIB=lo -SV_MODULE_FUNC=lo_address_new -SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z') -SV_MODULE_FAILED=1 -if test -n "$liblo_LIBS" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $liblo_CFLAGS" - LIBS="$LIBS $liblo_LIBS" - SV_MODULE_FAILED="" -fi -if test -z "$SV_MODULE_VERSION_TEST" ; then - SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE -fi -if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then - -pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5 -$as_echo_n "checking for liblo... " >&6; } - -if test -n "$liblo_CFLAGS"; then - pkg_cv_liblo_CFLAGS="$liblo_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$liblo_LIBS"; then - pkg_cv_liblo_LIBS="$liblo_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - else - liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$liblo_PKG_ERRORS" >&5 + echo "$portaudio_PKG_ERRORS" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} @@ -5545,166 +5604,11 @@ { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} else - liblo_CFLAGS=$pkg_cv_liblo_CFLAGS - liblo_LIBS=$pkg_cv_liblo_LIBS + portaudio_CFLAGS=$pkg_cv_portaudio_CFLAGS + portaudio_LIBS=$pkg_cv_portaudio_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED="" -fi -fi -if test -n "$SV_MODULE_FAILED"; then - as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` -ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED="" -else - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;} -fi - - - if test -z "$SV_MODULE_FAILED"; then - if test -n "$SV_MODULE_LIB"; then - as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 -$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$SV_MODULE_LIB $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $SV_MODULE_FUNC (); -int -main () -{ -return $SV_MODULE_FUNC (); - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - LIBS="$LIBS -l$SV_MODULE_LIB" -else - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;} -fi - - fi - fi -fi - - -SV_MODULE_MODULE=portaudio_2_0 -SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19" -SV_MODULE_HEADER=portaudio.h -SV_MODULE_LIB=portaudio -SV_MODULE_FUNC=Pa_IsFormatSupported -SV_MODULE_HAVE=HAVE_$(echo portaudio_2_0 | tr 'a-z' 'A-Z') -SV_MODULE_FAILED=1 -if test -n "$portaudio_2_0_LIBS" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS" - LIBS="$LIBS $portaudio_2_0_LIBS" - SV_MODULE_FAILED="" -fi -if test -z "$SV_MODULE_VERSION_TEST" ; then - SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE -fi -if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then - -pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio_2_0" >&5 -$as_echo_n "checking for portaudio_2_0... " >&6; } - -if test -n "$portaudio_2_0_CFLAGS"; then - pkg_cv_portaudio_2_0_CFLAGS="$portaudio_2_0_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_portaudio_2_0_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$portaudio_2_0_LIBS"; then - pkg_cv_portaudio_2_0_LIBS="$portaudio_2_0_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_portaudio_2_0_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - else - portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$portaudio_2_0_PKG_ERRORS" >&5 - - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -else - portaudio_2_0_CFLAGS=$pkg_cv_portaudio_2_0_CFLAGS - portaudio_2_0_LIBS=$pkg_cv_portaudio_2_0_LIBS - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS";LIBS="$LIBS $portaudio_2_0_LIBS";SV_MODULE_FAILED="" + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS";LIBS="$LIBS $portaudio_LIBS";SV_MODULE_FAILED="" fi fi if test -n "$SV_MODULE_FAILED"; then diff -r 428ce32a8dd9 -r a2a8fa0eed08 configure.ac --- a/configure.ac Tue Jul 14 15:04:45 2015 +0100 +++ b/configure.ac Wed Apr 20 12:06:28 2016 +0100 @@ -25,6 +25,9 @@ AC_PROG_INSTALL AC_PROG_MKDIR_P +# We are daringly making use of C++11 now +AX_CXX_COMPILE_STDCXX_11(noext) + AC_HEADER_STDC # These are the flags Autoconf guesses for us; we use them later if @@ -50,9 +53,10 @@ CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS" if test "x$GCC" = "xyes"; then - CXXFLAGS_DEBUG="-Wall -Woverloaded-virtual -Wextra -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -g -pipe" - CXXFLAGS_RELEASE="-g0 -O2 -Wall -pipe" - CXXFLAGS_MINIMAL="-g0 -O0" + CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe" + CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g" + CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2" + CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0" fi CXXFLAGS_BUILD="$CXXFLAGS_RELEASE" @@ -79,12 +83,10 @@ SV_MODULE_REQUIRED([fftw3f],[fftw3f >= 3.0.0],[fftw3.h],[fftw3f],[fftwf_execute]) SV_MODULE_REQUIRED([sndfile],[sndfile >= 1.0.16],[sndfile.h],[sndfile],[sf_open]) SV_MODULE_REQUIRED([samplerate],[samplerate >= 0.1.2],[samplerate.h],[samplerate],[src_new]) -SV_MODULE_REQUIRED([vamp],[vamp >= 2.1],[vamp/vamp.h],[],[]) -SV_MODULE_REQUIRED([vamphostsdk],[vamp-hostsdk >= 2.5],[vamp-hostsdk/PluginLoader.h],[vamp-hostsdk],[libvamphostsdk_v_2_5_present]) SV_MODULE_REQUIRED([rubberband],[rubberband],[rubberband/RubberBandStretcher.h],[rubberband],[rubberband_new]) SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new]) -SV_MODULE_OPTIONAL([portaudio_2_0],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported]) +SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported]) SV_MODULE_OPTIONAL([JACK],[jack >= 0.100],[jack/jack.h],[jack],[jack_client_open]) SV_MODULE_OPTIONAL([libpulse],[libpulse >= 0.9],[pulse/pulseaudio.h],[pulse],[pa_stream_new]) SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init]) diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/Document.cpp --- a/framework/Document.cpp Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/Document.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -19,6 +19,8 @@ #include "data/model/WritableWaveFileModel.h" #include "data/model/DenseThreeDimensionalModel.h" #include "data/model/DenseTimeValueModel.h" +#include "data/model/FlexiNoteModel.h" + #include "layer/Layer.h" #include "widgets/CommandHistory.h" #include "base/Command.h" @@ -27,6 +29,7 @@ #include "base/PlayParameters.h" #include "transform/TransformFactory.h" #include "transform/ModelTransformerFactory.h" +#include "transform/FeatureExtractionModelTransformer.h" #include #include #include @@ -38,6 +41,8 @@ #include "data/model/SparseTimeValueModel.h" #include "data/model/AlignmentModel.h" +using std::vector; + //#define DEBUG_DOCUMENT 1 //!!! still need to handle command history, documentRestored/documentModified @@ -46,9 +51,15 @@ m_mainModel(0), m_autoAlignment(false) { - connect(this, SIGNAL(modelAboutToBeDeleted(Model *)), + connect(this, + SIGNAL(modelAboutToBeDeleted(Model *)), ModelTransformerFactory::getInstance(), SLOT(modelAboutToBeDeleted(Model *))); + + connect(ModelTransformerFactory::getInstance(), + SIGNAL(transformFailed(QString, QString)), + this, + SIGNAL(modelGenerationFailed(QString, QString))); } Document::~Document() @@ -100,7 +111,6 @@ emit mainModelChanged(0); delete m_mainModel; - } Layer * @@ -207,56 +217,191 @@ Document::createDerivedLayer(const Transform &transform, const ModelTransformer::Input &input) { + Transforms transforms; + transforms.push_back(transform); + vector layers = createDerivedLayers(transforms, input); + if (layers.empty()) return 0; + else return layers[0]; +} + +vector +Document::createDerivedLayers(const Transforms &transforms, + const ModelTransformer::Input &input) +{ QString message; - Model *newModel = addDerivedModel(transform, input, message); - if (!newModel) { - emit modelGenerationFailed(transform.getIdentifier(), message); - return 0; + vector newModels = addDerivedModels(transforms, input, message, 0); + + if (newModels.empty()) { + //!!! This identifier may be wrong! + emit modelGenerationFailed(transforms[0].getIdentifier(), message); + return vector(); } else if (message != "") { - emit modelGenerationWarning(transform.getIdentifier(), message); + //!!! This identifier may be wrong! + emit modelGenerationWarning(transforms[0].getIdentifier(), message); } - LayerFactory::LayerTypeSet types = - LayerFactory::getInstance()->getValidLayerTypes(newModel); - - if (types.empty()) { - cerr << "WARNING: Document::createLayerForTransformer: no valid display layer for output of transform " << transform.getIdentifier() << endl; - newModel->aboutToDelete(); - emit modelAboutToBeDeleted(newModel); - m_models.erase(newModel); - delete newModel; - return 0; + QStringList names; + for (int i = 0; i < (int)newModels.size(); ++i) { + names.push_back(getUniqueLayerName + (TransformFactory::getInstance()-> + getTransformFriendlyName + (transforms[i].getIdentifier()))); } - //!!! for now, just use the first suitable layer type + vector layers = createLayersForDerivedModels(newModels, names); + return layers; +} - Layer *newLayer = createLayer(*types.begin()); - setModel(newLayer, newModel); - - //!!! We need to clone the model when adding the layer, so that it - //can be edited without affecting other layers that are based on - //the same model. Unfortunately we can't just clone it now, - //because it probably hasn't been completed yet -- the transform - //runs in the background. Maybe the transform has to handle - //cloning and cacheing models itself. - // - // Once we do clone models here, of course, we'll have to avoid - // leaking them too. - // - // We want the user to be able to add a model to a second layer - // _while it's still being calculated in the first_ and have it - // work quickly. That means we need to put the same physical - // model pointer in both layers, so they can't actually be cloned. - - if (newLayer) { - newLayer->setObjectName(getUniqueLayerName - (TransformFactory::getInstance()-> - getTransformFriendlyName - (transform.getIdentifier()))); +class AdditionalModelConverter : + public ModelTransformerFactory::AdditionalModelHandler +{ +public: + AdditionalModelConverter(Document *doc, + Document::LayerCreationHandler *handler) : + m_doc(doc), + m_handler(handler) { } - emit layerAdded(newLayer); - return newLayer; + virtual ~AdditionalModelConverter() { } + + void + setPrimaryLayers(vector layers) { + m_primary = layers; + } + + void + moreModelsAvailable(vector models) { + std::cerr << "AdditionalModelConverter::moreModelsAvailable: " << models.size() << " model(s)" << std::endl; + // We can't automatically regenerate the additional models on + // reload -- we should delete them instead + QStringList names; + foreach (Model *model, models) { + m_doc->addAdditionalModel(model); + names.push_back(QString()); + } + vector layers = m_doc->createLayersForDerivedModels + (models, names); + m_handler->layersCreated(this, m_primary, layers); + delete this; + } + + void + noMoreModelsAvailable() { + std::cerr << "AdditionalModelConverter::noMoreModelsAvailable" << std::endl; + m_handler->layersCreated(this, m_primary, vector()); + delete this; + } + + void cancel() { + foreach (Layer *layer, m_primary) { + Model *model = layer->getModel(); + if (model) { + model->abandon(); + } + } + } + +private: + Document *m_doc; + vector m_primary; + Document::LayerCreationHandler *m_handler; //!!! how to handle destruction of this? +}; + +Document::LayerCreationAsyncHandle +Document::createDerivedLayersAsync(const Transforms &transforms, + const ModelTransformer::Input &input, + LayerCreationHandler *handler) +{ + QString message; + + AdditionalModelConverter *amc = new AdditionalModelConverter(this, handler); + + vector newModels = addDerivedModels + (transforms, input, message, amc); + + QStringList names; + for (int i = 0; i < (int)newModels.size(); ++i) { + names.push_back(getUniqueLayerName + (TransformFactory::getInstance()-> + getTransformFriendlyName + (transforms[i].getIdentifier()))); + } + + vector layers = createLayersForDerivedModels(newModels, names); + amc->setPrimaryLayers(layers); + + if (newModels.empty()) { + //!!! This identifier may be wrong! + emit modelGenerationFailed(transforms[0].getIdentifier(), message); + //!!! what to do with amc? + } else if (message != "") { + //!!! This identifier may be wrong! + emit modelGenerationWarning(transforms[0].getIdentifier(), message); + //!!! what to do with amc? + } + + return amc; +} + +void +Document::cancelAsyncLayerCreation(Document::LayerCreationAsyncHandle h) +{ + AdditionalModelConverter *conv = static_cast(h); + conv->cancel(); +} + +vector +Document::createLayersForDerivedModels(vector newModels, + QStringList names) +{ + vector layers; + + for (int i = 0; i < (int)newModels.size(); ++i) { + + Model *newModel = newModels[i]; + + LayerFactory::LayerTypeSet types = + LayerFactory::getInstance()->getValidLayerTypes(newModel); + + if (types.empty()) { + cerr << "WARNING: Document::createLayerForTransformer: no valid display layer for output of transform " << names[i] << endl; + //!!! inadequate cleanup: + newModel->aboutToDelete(); + emit modelAboutToBeDeleted(newModel); + m_models.erase(newModel); + delete newModel; + return vector(); + } + + //!!! for now, just use the first suitable layer type + + Layer *newLayer = createLayer(*types.begin()); + setModel(newLayer, newModel); + + //!!! We need to clone the model when adding the layer, so that it + //can be edited without affecting other layers that are based on + //the same model. Unfortunately we can't just clone it now, + //because it probably hasn't been completed yet -- the transform + //runs in the background. Maybe the transform has to handle + //cloning and cacheing models itself. + // + // Once we do clone models here, of course, we'll have to avoid + // leaking them too. + // + // We want the user to be able to add a model to a second layer + // _while it's still being calculated in the first_ and have it + // work quickly. That means we need to put the same physical + // model pointer in both layers, so they can't actually be cloned. + + if (newLayer) { + newLayer->setObjectName(names[i]); + } + + emit layerAdded(newLayer); + layers.push_back(newLayer); + } + + return layers; } void @@ -374,10 +519,9 @@ << typeid(*replacementModel).name() << ") in layer " << layer << " (name " << layer->objectName() << ")" << endl; -#endif + RangeSummarisableTimeValueModel *rm = dynamic_cast(replacementModel); -#ifdef DEBUG_DOCUMENT if (rm) { cerr << "new model has " << rm->getChannelCount() << " channels " << endl; } else { @@ -394,6 +538,14 @@ } for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + if (i->second.additional) { + Model *m = i->first; + emit modelAboutToBeDeleted(m); + delete m; + } + } + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { Model *m = i->first; @@ -419,6 +571,7 @@ } if (m_autoAlignment) { + SVDEBUG << "Document::setMainModel: auto-alignment is on, aligning model if possible" << endl; alignModel(m_mainModel); } @@ -428,21 +581,21 @@ } void -Document::addDerivedModel(const Transform &transform, - const ModelTransformer::Input &input, - Model *outputModelToAdd) +Document::addAlreadyDerivedModel(const Transform &transform, + const ModelTransformer::Input &input, + Model *outputModelToAdd) { if (m_models.find(outputModelToAdd) != m_models.end()) { - cerr << "WARNING: Document::addDerivedModel: Model already added" + cerr << "WARNING: Document::addAlreadyDerivedModel: Model already added" << endl; return; } #ifdef DEBUG_DOCUMENT if (input.getModel()) { - cerr << "Document::addDerivedModel: source is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl; + cerr << "Document::addAlreadyDerivedModel: source is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl; } else { - cerr << "Document::addDerivedModel: source is " << input.getModel() << endl; + cerr << "Document::addAlreadyDerivedModel: source is " << input.getModel() << endl; } #endif @@ -450,6 +603,7 @@ rec.source = input.getModel(); rec.channel = input.getChannel(); rec.transform = transform; + rec.additional = false; rec.refcount = 0; outputModelToAdd->setSourceModel(input.getModel()); @@ -457,7 +611,7 @@ m_models[outputModelToAdd] = rec; #ifdef DEBUG_DOCUMENT - SVDEBUG << "Document::addDerivedModel: Added model " << outputModelToAdd << endl; + cerr << "Document::addAlreadyDerivedModel: Added model " << outputModelToAdd << endl; cerr << "Models now: "; for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { cerr << i->first << " "; @@ -480,7 +634,9 @@ ModelRecord rec; rec.source = 0; + rec.channel = 0; rec.refcount = 0; + rec.additional = false; m_models[model] = rec; @@ -493,7 +649,46 @@ cerr << endl; #endif - if (m_autoAlignment) alignModel(model); + if (m_autoAlignment) { + SVDEBUG << "Document::addImportedModel: auto-alignment is on, aligning model if possible" << endl; + alignModel(model); + } else { + SVDEBUG << "Document(" << this << "): addImportedModel: auto-alignment is off" << endl; + } + + emit modelAdded(model); +} + +void +Document::addAdditionalModel(Model *model) +{ + if (m_models.find(model) != m_models.end()) { + cerr << "WARNING: Document::addAdditionalModel: Model already added" + << endl; + return; + } + + ModelRecord rec; + rec.source = 0; + rec.channel = 0; + rec.refcount = 0; + rec.additional = true; + + m_models[model] = rec; + +#ifdef DEBUG_DOCUMENT + SVDEBUG << "Document::addAdditionalModel: Added model " << model << endl; + cerr << "Models now: "; + for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { + cerr << i->first << " "; + } + cerr << endl; +#endif + + if (m_autoAlignment) { + SVDEBUG << "Document::addAdditionalModel: auto-alignment is on, aligning model if possible" << endl; + alignModel(model); + } emit modelAdded(model); } @@ -503,40 +698,59 @@ const ModelTransformer::Input &input, QString &message) { - Model *model = 0; - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { - if (i->second.transform == transform && - i->second.source == input.getModel() && + if (i->second.transform == transform && + i->second.source == input.getModel() && i->second.channel == input.getChannel()) { - return i->first; - } + std::cerr << "derived model taken from map " << std::endl; + return i->first; + } } - model = ModelTransformerFactory::getInstance()->transform - (transform, input, message); + Transforms tt; + tt.push_back(transform); + vector mm = addDerivedModels(tt, input, message, 0); + if (mm.empty()) return 0; + else return mm[0]; +} - // The transform we actually used was presumably identical to the - // one asked for, except that the version of the plugin may - // differ. It's possible that the returned message contains a - // warning about this; that doesn't concern us here, but we do - // need to ensure that the transform we remember is correct for - // what was actually applied, with the current plugin version. +vector +Document::addDerivedModels(const Transforms &transforms, + const ModelTransformer::Input &input, + QString &message, + AdditionalModelConverter *amc) +{ + vector mm = + ModelTransformerFactory::getInstance()->transformMultiple + (transforms, input, message, amc); - Transform applied = transform; - applied.setPluginVersion - (TransformFactory::getInstance()-> - getDefaultTransformFor(transform.getIdentifier(), - lrintf(transform.getSampleRate())) - .getPluginVersion()); + for (int j = 0; j < (int)mm.size(); ++j) { - if (!model) { - cerr << "WARNING: Document::addDerivedModel: no output model for transform " << transform.getIdentifier() << endl; - } else { - addDerivedModel(applied, input, model); + Model *model = mm[j]; + + // The transform we actually used was presumably identical to + // the one asked for, except that the version of the plugin + // may differ. It's possible that the returned message + // contains a warning about this; that doesn't concern us + // here, but we do need to ensure that the transform we + // remember is correct for what was actually applied, with the + // current plugin version. + + Transform applied = transforms[j]; + applied.setPluginVersion + (TransformFactory::getInstance()-> + getDefaultTransformFor(applied.getIdentifier(), + applied.getSampleRate()) + .getPluginVersion()); + + if (!model) { + cerr << "WARNING: Document::addDerivedModel: no output model for transform " << applied.getIdentifier() << endl; + } else { + addAlreadyDerivedModel(applied, input, model); + } } - - return model; + + return mm; } void @@ -690,6 +904,7 @@ } LayerFactory::getInstance()->setModel(layer, model); + // std::cerr << "layer type: " << LayerFactory::getInstance()->getLayerTypeName(LayerFactory::getInstance()->getLayerType(layer)) << std::endl; if (previousModel) { releaseModel(previousModel); @@ -846,14 +1061,22 @@ void Document::alignModel(Model *model) { - if (!m_mainModel) return; + SVDEBUG << "Document::alignModel(" << model << ")" << endl; + + if (!m_mainModel) { + SVDEBUG << "(no main model to align to)" << endl; + return; + } RangeSummarisableTimeValueModel *rm = dynamic_cast(model); - if (!rm) return; + if (!rm) { + SVDEBUG << "(main model is not alignable-to)" << endl; + return; + } if (rm->getAlignmentReference() == m_mainModel) { - SVDEBUG << "Document::alignModel: model " << rm << " is already aligned to main model " << m_mainModel << endl; + SVDEBUG << "(model " << rm << " is already aligned to main model " << m_mainModel << ")" << endl; return; } @@ -1013,6 +1236,7 @@ m_d(d), m_view(view), m_layer(layer), + m_wasDormant(layer->isLayerDormant(view)), m_name(qApp->translate("RemoveLayerCommand", "Delete %1 Layer").arg(layer->objectName())), m_added(true) { @@ -1066,7 +1290,7 @@ Document::RemoveLayerCommand::unexecute() { m_view->addLayer(m_layer); - m_layer->setLayerDormant(m_view, false); + m_layer->setLayerDormant(m_view, m_wasDormant); m_d->addToLayerViewMap(m_layer, m_view); m_added = true; diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/Document.h --- a/framework/Document.h Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/Document.h Wed Apr 20 12:06:28 2016 +0100 @@ -19,6 +19,7 @@ #include "layer/LayerFactory.h" #include "transform/Transform.h" #include "transform/ModelTransformer.h" +#include "transform/FeatureExtractionModelTransformer.h" #include "base/Command.h" #include @@ -29,6 +30,8 @@ class View; class WaveFileModel; +class AdditionalModelConverter; + /** * A Sonic Visualiser document consists of a set of data models, and * also the visualisation layers used to display them. Changes to the @@ -117,6 +120,56 @@ const ModelTransformer::Input &); /** + * Create and return suitable layers for the given transforms, + * which must be identical apart from the output (i.e. must use + * the same plugin and configuration). The layers are returned in + * the same order as the transforms are supplied. + */ + std::vector createDerivedLayers(const Transforms &, + const ModelTransformer::Input &); + + typedef void *LayerCreationAsyncHandle; + + class LayerCreationHandler { + public: + virtual ~LayerCreationHandler() { } + + /** + * The primary layers are those corresponding 1-1 to the input + * models, listed in the same order as the input models. The + * additional layers vector contains any layers (from all + * models) that were returned separately at the end of + * processing. The handle indicates which async process this + * callback was initiated by. It must not be used again after + * this function returns. + */ + virtual void layersCreated(LayerCreationAsyncHandle handle, + std::vector primary, + std::vector additional) = 0; + }; + + /** + * Create suitable layers for the given transforms, which must be + * identical apart from the output (i.e. must use the same plugin + * and configuration). This method returns after initialising the + * transformer process, and the layers are returned through a + * subsequent call to the provided handler (which must be + * non-null). The handle returned will be passed through to the + * handler callback, and may be also used for cancelling the task. + */ + LayerCreationAsyncHandle createDerivedLayersAsync(const Transforms &, + const ModelTransformer::Input &, + LayerCreationHandler *handler); + + /** + * Indicate that the async layer creation task associated with the + * given handle should be cancelled. There is no guarantee about + * what this will mean, and the handler callback may still be + * called. + */ + void cancelAsyncLayerCreation(LayerCreationAsyncHandle handle); + + /** * Delete the given layer, and also its associated model if no * longer used by any other layer. In general, this should be the * only method used to delete layers -- doing so directly is a bit @@ -154,13 +207,24 @@ QString &returnedMessage); /** + * Add derived models associated with the given set of related + * transforms, running the transforms and returning the resulting + * models. + */ + friend class AdditionalModelConverter; + std::vector addDerivedModels(const Transforms &transforms, + const ModelTransformer::Input &input, + QString &returnedMessage, + AdditionalModelConverter *); + + /** * Add a derived model associated with the given transform. This * is necessary to register any derived model that was not created * by the document using createDerivedModel or createDerivedLayer. */ - void addDerivedModel(const Transform &transform, - const ModelTransformer::Input &input, - Model *outputModelToAdd); + void addAlreadyDerivedModel(const Transform &transform, + const ModelTransformer::Input &input, + Model *outputModelToAdd); /** * Add an imported (non-derived, non-main) model. This is @@ -190,7 +254,9 @@ void addLayerToView(View *, Layer *); /** - * Remove the given layer from the given view. + * Remove the given layer from the given view. Ownership of the + * layer is transferred to a command object on the undo stack, and + * the layer will be deleted when the undo stack is pruned. */ void removeLayerFromView(View *, Layer *); @@ -279,6 +345,7 @@ const Model *source; int channel; Transform transform; + bool additional; // Count of the number of layers using this model. int refcount; @@ -287,6 +354,12 @@ typedef std::map ModelMap; ModelMap m_models; + /** + * Add an extra derived model (returned at the end of processing a + * transform). + */ + void addAdditionalModel(Model *); + class AddLayerCommand : public Command { public: @@ -319,6 +392,7 @@ Document *m_d; View *m_view; // I don't own this Layer *m_layer; // Document owns this, but I determine its lifespan + bool m_wasDormant; QString m_name; bool m_added; }; @@ -338,6 +412,9 @@ void toXml(QTextStream &, QString, QString, bool asTemplate) const; void writePlaceholderMainModel(QTextStream &, QString) const; + std::vector createLayersForDerivedModels(std::vector, + QStringList names); + /** * And these are the layers. We also control the lifespans of * these (usually through the commands used to add and remove them). diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/MainWindowBase.cpp --- a/framework/MainWindowBase.cpp Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/MainWindowBase.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -16,12 +16,13 @@ #include "MainWindowBase.h" #include "Document.h" - #include "view/Pane.h" #include "view/PaneStack.h" -#include "data/model/WaveFileModel.h" +#include "data/model/ReadOnlyWaveFileModel.h" +#include "data/model/WritableWaveFileModel.h" #include "data/model/SparseOneDimensionalModel.h" #include "data/model/NoteModel.h" +#include "data/model/FlexiNoteModel.h" #include "data/model/Labeller.h" #include "data/model/TabularModel.h" #include "view/ViewManager.h" @@ -35,6 +36,7 @@ #include "layer/SliceableLayer.h" #include "layer/ImageLayer.h" #include "layer/NoteLayer.h" +#include "layer/FlexiNoteLayer.h" #include "layer/RegionLayer.h" #include "widgets/ListInputDialog.h" @@ -43,23 +45,21 @@ #include "widgets/MIDIFileImportDialog.h" #include "widgets/CSVFormatDialog.h" #include "widgets/ModelDataTableDialog.h" - -#include "audioio/AudioCallbackPlaySource.h" -#include "audioio/AudioCallbackPlayTarget.h" -#include "audioio/AudioTargetFactory.h" -#include "audioio/PlaySpeedRangeMapper.h" +#include "widgets/InteractiveFileFinder.h" + +#include "audio/AudioCallbackPlaySource.h" +#include "audio/AudioRecordTarget.h" +#include "audio/PlaySpeedRangeMapper.h" + #include "data/fileio/DataFileReaderFactory.h" #include "data/fileio/PlaylistFileReader.h" #include "data/fileio/WavFileWriter.h" -#include "data/fileio/CSVFileWriter.h" #include "data/fileio/MIDIFileWriter.h" #include "data/fileio/BZipFileDevice.h" #include "data/fileio/FileSource.h" #include "data/fileio/AudioFileReaderFactory.h" #include "rdf/RDFImporter.h" -#include "data/fft/FFTDataServer.h" - #include "base/RecentFiles.h" #include "base/PlayParameterRepository.h" @@ -73,6 +73,10 @@ #include "data/osc/OSCQueue.h" #include "data/midi/MIDIInput.h" +#include +#include +#include + #include #include #include @@ -87,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -96,14 +101,12 @@ #include #include #include +#include #include #include #include - - - using std::vector; using std::map; using std::set; @@ -130,16 +133,16 @@ #undef Window #endif -MainWindowBase::MainWindowBase(bool withAudioOutput, - bool withOSCSupport, - bool withMIDIInput) : +MainWindowBase::MainWindowBase(SoundOptions options) : m_document(0), m_paneStack(0), m_viewManager(0), m_timeRulerLayer(0), - m_audioOutput(withAudioOutput), + m_soundOptions(options), m_playSource(0), + m_recordTarget(0), m_playTarget(0), + m_audioIO(0), m_oscQueue(0), m_oscQueueStarter(0), m_midiInput(0), @@ -149,14 +152,30 @@ m_openingAudioFile(false), m_abandoning(false), m_labeller(0), - m_lastPlayStatusSec(0) + m_lastPlayStatusSec(0), + m_initialDarkBackground(false), + m_defaultFfwdRwdStep(2, 0), + m_audioRecordMode(RecordCreateAdditionalModel), + m_statusLabel(0), + m_menuShortcutMapper(0) { Profiler profiler("MainWindowBase::MainWindowBase"); + if (options & WithAudioInput) { + if (!(options & WithAudioOutput)) { + cerr << "WARNING: MainWindowBase: WithAudioInput requires WithAudioOutput -- recording will not work" << endl; + } + } + + qRegisterMetaType("sv_frame_t"); + qRegisterMetaType("sv_samplerate_t"); + #ifdef Q_WS_X11 XSetErrorHandler(handle_x11_error); #endif + connect(this, SIGNAL(hideSplash()), this, SLOT(emitHideSplash())); + connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), this, SLOT(documentModified())); connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), @@ -170,13 +189,14 @@ // set a sensible default font size for views -- cannot do this // in Preferences, which is in base and not supposed to use QtGui - int viewFontSize = QApplication::font().pointSize() * 0.9; + int viewFontSize = int(QApplication::font().pointSize() * 0.9); QSettings settings; settings.beginGroup("Preferences"); viewFontSize = settings.value("view-font-size", viewFontSize).toInt(); settings.setValue("view-font-size", viewFontSize); settings.endGroup(); +#ifdef NOT_DEFINED // This no longer works correctly on any platform AFAICS Preferences::BackgroundMode mode = Preferences::getInstance()->getBackgroundMode(); m_initialDarkBackground = m_viewManager->getGlobalDarkBackground(); @@ -184,6 +204,7 @@ m_viewManager->setGlobalDarkBackground (mode == Preferences::DarkBackground); } +#endif m_paneStack = new PaneStack(0, m_viewManager); connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), @@ -209,9 +230,15 @@ m_playSource = new AudioCallbackPlaySource(m_viewManager, QApplication::applicationName()); - - connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)), - this, SLOT(sampleRateMismatch(size_t, size_t, bool))); + if (m_soundOptions & WithAudioInput) { + m_recordTarget = new AudioRecordTarget(m_viewManager, + QApplication::applicationName()); + connect(m_recordTarget, SIGNAL(recordDurationChanged(sv_frame_t, sv_samplerate_t)), + this, SLOT(recordDurationChanged(sv_frame_t, sv_samplerate_t))); + } + + connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)), + this, SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool))); connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), this, SLOT(audioOverloadPluginDisabled())); connect(m_playSource, SIGNAL(audioTimeStretchMultiChannelDisabled()), @@ -220,17 +247,17 @@ connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)), this, SLOT(outputLevelsChanged(float, float))); - connect(m_viewManager, SIGNAL(playbackFrameChanged(unsigned long)), - this, SLOT(playbackFrameChanged(unsigned long))); - - connect(m_viewManager, SIGNAL(globalCentreFrameChanged(unsigned long)), - this, SLOT(globalCentreFrameChanged(unsigned long))); - - connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)), - this, SLOT(viewCentreFrameChanged(View *, unsigned long))); - - connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)), - this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool))); + connect(m_viewManager, SIGNAL(playbackFrameChanged(sv_frame_t)), + this, SLOT(playbackFrameChanged(sv_frame_t))); + + connect(m_viewManager, SIGNAL(globalCentreFrameChanged(sv_frame_t)), + this, SLOT(globalCentreFrameChanged(sv_frame_t))); + + connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, sv_frame_t)), + this, SLOT(viewCentreFrameChanged(View *, sv_frame_t))); + + connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, int, bool)), + this, SLOT(viewZoomLevelChanged(View *, int, bool))); connect(Preferences::getInstance(), SIGNAL(propertyChanged(PropertyContainer::PropertyName)), @@ -249,30 +276,171 @@ m_labeller = new Labeller(labellerType); m_labeller->setCounterCycleSize(cycle); - if (withMIDIInput) { + if (m_soundOptions & WithMIDIInput) { m_midiInput = new MIDIInput(QApplication::applicationName(), this); } - if (withOSCSupport) { - m_oscQueueStarter = new OSCQueueStarter(this); - connect(m_oscQueueStarter, SIGNAL(finished()), this, SLOT(oscReady())); - m_oscQueueStarter->start(); - } + QTimer::singleShot(1500, this, SIGNAL(hideSplash())); } MainWindowBase::~MainWindowBase() { SVDEBUG << "MainWindowBase::~MainWindowBase" << endl; - if (m_playTarget) m_playTarget->shutdown(); -// delete m_playTarget; + delete m_playTarget; delete m_playSource; + delete m_audioIO; + delete m_recordTarget; delete m_viewManager; delete m_oscQueue; + delete m_oscQueueStarter; delete m_midiInput; Profiles::getInstance()->dump(); } void +MainWindowBase::emitHideSplash() +{ + emit hideSplash(this); +} + +void +MainWindowBase::finaliseMenus() +{ + delete m_menuShortcutMapper; + m_menuShortcutMapper = 0; + + foreach (QShortcut *sc, m_appShortcuts) { + delete sc; + } + m_appShortcuts.clear(); + + QMenuBar *mb = menuBar(); + + // This used to find all children of QMenu type, and call + // finaliseMenu on those. But it seems we are getting hold of some + // menus that way that are not actually active in the menu bar and + // are not returned in their parent menu's actions() list, and if + // we finalise those, we end up with duplicate shortcuts in the + // app shortcut mapper. So we should do this by descending the + // menu tree through only those menus accessible via actions() + // from their parents instead. + + QList menus = mb->findChildren + (QString(), Qt::FindDirectChildrenOnly); + + foreach (QMenu *menu, menus) { + if (menu) finaliseMenu(menu); + } +} + +void +MainWindowBase::finaliseMenu(QMenu * +#ifdef Q_OS_MAC + menu +#endif + ) +{ +#ifdef Q_OS_MAC + // See https://bugreports.qt-project.org/browse/QTBUG-38256 and + // our issue #890 http://code.soundsoftware.ac.uk/issues/890 -- + // single-key shortcuts that are associated only with a menu + // action (and not with a toolbar button) do not work with Qt 5.x + // under OS/X. + // + // Apparently Cocoa never handled them as a matter of course, but + // earlier versions of Qt picked them up as widget shortcuts and + // handled them anyway. That behaviour was removed to fix a crash + // when invoking a menu while its window was overridden by a modal + // dialog (https://bugreports.qt-project.org/browse/QTBUG-30657). + // + // This workaround restores the single-key shortcut behaviour by + // searching in menus for single-key shortcuts that are associated + // only with the menu and not with a toolbar button, and + // augmenting them with global application shortcuts that invoke + // the relevant actions, testing whether the actions are enabled + // on invocation. + // + // (Previously this acted on all single-key shortcuts in menus, + // and it removed the shortcut from the action when it created + // each new global one, in order to avoid an "ambiguous shortcut" + // error in the case where the action was also associated with a + // toolbar button. But that has the unwelcome side-effect of + // removing the shortcut hint from the menu entry. So now we leave + // the shortcut in the menu action as well as creating a global + // one, and we only act on shortcuts that have no toolbar button, + // i.e. that will not otherwise work. The downside is that if this + // bug is fixed in a future Qt release, we will start getting + // "ambiguous shortcut" errors from the menu entry actions and + // will need to update the code.) + + // Update: The bug was fixed in Qt 5.4 for shortcuts with no + // modifier, and I believe it is fixed in Qt 5.5 for shortcuts + // with Shift modifiers. The below reflects that + +#if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0)) + + if (!m_menuShortcutMapper) { + m_menuShortcutMapper = new QSignalMapper(this); + connect(m_menuShortcutMapper, SIGNAL(mapped(QObject *)), + this, SLOT(menuActionMapperInvoked(QObject *))); + } + + foreach (QAction *a, menu->actions()) { + + if (a->isSeparator()) { + continue; + } else if (a->menu()) { + finaliseMenu(a->menu()); + } else { + + QWidgetList ww = a->associatedWidgets(); + bool hasButton = false; + foreach (QWidget *w, ww) { + if (qobject_cast(w)) { + hasButton = true; + break; + } + } + if (hasButton) continue; + QKeySequence sc = a->shortcut(); + + // Note that the set of "single-key shortcuts" that aren't + // working and that we need to handle here includes those + // with the Shift modifier mask as well as those with no + // modifier at all +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + // Nothing needed + if (false) { +#elif (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) + if (sc.count() == 1 && + (sc[0] & Qt::KeyboardModifierMask) == Qt::ShiftModifier) { +#else + if (sc.count() == 1 && + ((sc[0] & Qt::KeyboardModifierMask) == Qt::NoModifier || + (sc[0] & Qt::KeyboardModifierMask) == Qt::ShiftModifier)) { +#endif + QShortcut *newSc = new QShortcut(sc, a->parentWidget()); + QObject::connect(newSc, SIGNAL(activated()), + m_menuShortcutMapper, SLOT(map())); + m_menuShortcutMapper->setMapping(newSc, a); + m_appShortcuts.push_back(newSc); + } + } + } +#endif +#endif +} + +void +MainWindowBase::menuActionMapperInvoked(QObject *o) +{ + QAction *a = qobject_cast(o); + if (a && a->isEnabled()) { + a->trigger(); + } +} + +void MainWindowBase::resizeConstrained(QSize size) { QDesktopWidget *desktop = QApplication::desktop(); @@ -283,6 +451,14 @@ } void +MainWindowBase::startOSCQueue() +{ + m_oscQueueStarter = new OSCQueueStarter(this); + connect(m_oscQueueStarter, SIGNAL(finished()), this, SLOT(oscReady())); + m_oscQueueStarter->start(); +} + +void MainWindowBase::oscReady() { if (m_oscQueue && m_oscQueue->isOK()) { @@ -298,24 +474,8 @@ MainWindowBase::getOpenFileName(FileFinder::FileType type) { FileFinder *ff = FileFinder::getInstance(); - switch (type) { - case FileFinder::SessionFile: - return ff->getOpenFileName(type, m_sessionFile); - case FileFinder::AudioFile: - return ff->getOpenFileName(type, m_audioFile); - case FileFinder::LayerFile: - return ff->getOpenFileName(type, m_sessionFile); - case FileFinder::LayerFileNoMidi: - return ff->getOpenFileName(type, m_sessionFile); - case FileFinder::SessionOrAudioFile: - return ff->getOpenFileName(type, m_sessionFile); - case FileFinder::ImageFile: - return ff->getOpenFileName(type, m_sessionFile); - case FileFinder::CSVFile: - return ff->getOpenFileName(type, m_sessionFile); - case FileFinder::IMAFile: - return ff->getOpenFileName(type, m_audioFile); - case FileFinder::AnyFile: + + if (type == FileFinder::AnyFile) { if (getMainModel() != 0 && m_paneStack != 0 && m_paneStack->getCurrentPane() != 0) { // can import a layer @@ -324,33 +484,29 @@ return ff->getOpenFileName(FileFinder::SessionOrAudioFile, m_sessionFile); } + } + + QString lastPath = m_sessionFile; + + if (type == FileFinder::AudioFile || + type == FileFinder::IMAFile) { + lastPath = m_audioFile; } - return ""; + + return ff->getOpenFileName(type, lastPath); } QString MainWindowBase::getSaveFileName(FileFinder::FileType type) { + QString lastPath = m_sessionFile; + + if (type == FileFinder::AudioFile) { + lastPath = m_audioFile; + } + FileFinder *ff = FileFinder::getInstance(); - switch (type) { - case FileFinder::SessionFile: - return ff->getSaveFileName(type, m_sessionFile); - case FileFinder::AudioFile: - return ff->getSaveFileName(type, m_audioFile); - case FileFinder::LayerFile: - return ff->getSaveFileName(type, m_sessionFile); - case FileFinder::LayerFileNoMidi: - return ff->getSaveFileName(type, m_sessionFile); - case FileFinder::SessionOrAudioFile: - return ff->getSaveFileName(type, m_sessionFile); - case FileFinder::ImageFile: - return ff->getSaveFileName(type, m_sessionFile); - case FileFinder::CSVFile: - return ff->getSaveFileName(type, m_sessionFile); - case FileFinder::AnyFile: - return ff->getSaveFileName(type, m_sessionFile); - } - return ""; + return ff->getSaveFileName(type, lastPath); } void @@ -398,14 +554,13 @@ break; } } - if (currentLayer) { - for (int i = 0; i < currentPane->getLayerCount(); ++i) { - if (currentPane->getLayer(i) == currentLayer) { - if (i > 0) havePrevLayer = true; - if (i < currentPane->getLayerCount()-1) haveNextLayer = true; - break; - } - } + // the prev/next layer commands actually include the pane + // itself as one of the selectables -- so we always have a + // prev and next layer, as long as we have a pane with at + // least one layer in it + if (currentPane->getLayerCount() > 0) { + havePrevLayer = true; + haveNextLayer = true; } } @@ -417,7 +572,7 @@ bool haveMainModel = (getMainModel() != 0); bool havePlayTarget = - (m_playTarget != 0); + (m_playTarget != 0 || m_audioIO != 0); bool haveSelection = (m_viewManager && !m_viewManager->getSelections().empty()); @@ -430,6 +585,7 @@ bool haveCurrentDurationLayer = (haveCurrentLayer && (dynamic_cast(currentLayer) || + dynamic_cast(currentLayer) || dynamic_cast(currentLayer))); bool haveCurrentColour3DPlot = (haveCurrentLayer && @@ -461,8 +617,9 @@ emit canMeasureLayer(haveCurrentLayer); emit canSelect(haveMainModel && haveCurrentPane); emit canPlay(haveMainModel && havePlayTarget); - emit canFfwd(true); - emit canRewind(true); + emit canRecord(m_recordTarget != 0); + emit canFfwd(haveMainModel); + emit canRewind(haveMainModel); emit canPaste(haveClipboardContents); emit canInsertInstant(haveCurrentPane); emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection); @@ -472,6 +629,7 @@ emit canClearSelection(haveSelection); emit canEditSelection(haveSelection && haveCurrentEditableLayer); emit canSave(m_sessionFile != "" && m_documentModified); + emit canSaveAs(haveMainModel); emit canSelectPreviousPane(havePrevPane); emit canSelectNextPane(haveNextPane); emit canSelectPreviousLayer(havePrevLayer); @@ -578,9 +736,9 @@ // frame before switching to whichever one we decide we want to // switch to, regardless of our efforts. - int frame = m_playSource->getCurrentBufferedFrame(); - -// cerr << "currentPaneChanged: current frame (in ref model) = " << frame << endl; + sv_frame_t frame = m_playSource->getCurrentBufferedFrame(); + + cerr << "currentPaneChanged: current frame (in ref model) = " << frame << endl; View::ModelSet soloModels = p->getModels(); @@ -667,7 +825,7 @@ Pane *currentPane = m_paneStack->getCurrentPane(); if (!currentPane) return; - size_t startFrame, endFrame; + sv_frame_t startFrame, endFrame; if (currentPane->getStartFrame() < 0) startFrame = 0; else startFrame = currentPane->getStartFrame(); @@ -738,24 +896,24 @@ void MainWindowBase::pasteAtPlaybackPosition() { - unsigned long pos = getFrame(); + sv_frame_t pos = getFrame(); Clipboard &clipboard = m_viewManager->getClipboard(); if (!clipboard.empty()) { - long firstEventFrame = clipboard.getPoints()[0].getFrame(); - long offset = 0; + sv_frame_t firstEventFrame = clipboard.getPoints()[0].getFrame(); + sv_frame_t offset = 0; if (firstEventFrame < 0) { - offset = (long)pos - firstEventFrame; + offset = pos - firstEventFrame; } else if (firstEventFrame < pos) { - offset = pos - (unsigned long)firstEventFrame; + offset = pos - firstEventFrame; } else { - offset = -((unsigned long)firstEventFrame - pos); + offset = -(firstEventFrame - pos); } pasteRelative(offset); } } void -MainWindowBase::pasteRelative(int offset) +MainWindowBase::pasteRelative(sv_frame_t offset) { Pane *currentPane = m_paneStack->getCurrentPane(); if (!currentPane) return; @@ -801,19 +959,21 @@ Layer *layer = m_paneStack->getCurrentPane()->getSelectedLayer(); - if (m_viewManager && - (m_viewManager->getToolMode() == ViewManager::MeasureMode)) { - - layer->deleteCurrentMeasureRect(); - - } else { - - MultiSelection::SelectionList selections = - m_viewManager->getSelections(); + if (m_viewManager) { + + if (m_viewManager->getToolMode() == ViewManager::MeasureMode) { + + layer->deleteCurrentMeasureRect(); - for (MultiSelection::SelectionList::iterator i = selections.begin(); - i != selections.end(); ++i) { - layer->deleteSelection(*i); + } else { + + MultiSelection::SelectionList selections = + m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->deleteSelection(*i); + } } } } @@ -821,7 +981,7 @@ // FrameTimer method -unsigned long +sv_frame_t MainWindowBase::getFrame() const { if (m_playSource && m_playSource->isPlaying()) { @@ -843,8 +1003,8 @@ MultiSelection::SelectionList selections = m_viewManager->getSelections(); for (MultiSelection::SelectionList::iterator i = selections.begin(); i != selections.end(); ++i) { - size_t start = i->getStartFrame(); - size_t end = i->getEndFrame(); + sv_frame_t start = i->getStartFrame(); + sv_frame_t end = i->getEndFrame(); if (start != end) { insertInstantAt(start); insertInstantAt(end); @@ -853,7 +1013,7 @@ } void -MainWindowBase::insertInstantAt(size_t frame) +MainWindowBase::insertInstantAt(sv_frame_t frame) { Pane *pane = m_paneStack->getCurrentPane(); if (!pane) { @@ -898,29 +1058,29 @@ SparseOneDimensionalModel::EditCommand *command = new SparseOneDimensionalModel::EditCommand(sodm, tr("Add Point")); - if (m_labeller->requiresPrevPoint()) { - - SparseOneDimensionalModel::PointList prevPoints = - sodm->getPreviousPoints(frame); - - if (!prevPoints.empty()) { - prevPoint = *prevPoints.begin(); - havePrevPoint = true; + if (m_labeller) { + + if (m_labeller->requiresPrevPoint()) { + + SparseOneDimensionalModel::PointList prevPoints = + sodm->getPreviousPoints(frame); + + if (!prevPoints.empty()) { + prevPoint = *prevPoints.begin(); + havePrevPoint = true; + } } - } - - if (m_labeller) { m_labeller->setSampleRate(sodm->getSampleRate()); - if (m_labeller->actingOnPrevPoint()) { + if (m_labeller->actingOnPrevPoint() && havePrevPoint) { command->deletePoint(prevPoint); } m_labeller->label (point, havePrevPoint ? &prevPoint : 0); - if (m_labeller->actingOnPrevPoint()) { + if (m_labeller->actingOnPrevPoint() && havePrevPoint) { command->addPoint(prevPoint); } } @@ -945,8 +1105,8 @@ MultiSelection::SelectionList selections = m_viewManager->getSelections(); for (MultiSelection::SelectionList::iterator i = selections.begin(); i != selections.end(); ++i) { - size_t start = i->getStartFrame(); - size_t end = i->getEndFrame(); + sv_frame_t start = i->getStartFrame(); + sv_frame_t end = i->getEndFrame(); if (start < end) { insertItemAt(start, end - start); } @@ -954,7 +1114,7 @@ } void -MainWindowBase::insertItemAt(size_t frame, size_t duration) +MainWindowBase::insertItemAt(sv_frame_t frame, sv_frame_t duration) { Pane *pane = m_paneStack->getCurrentPane(); if (!pane) { @@ -963,10 +1123,10 @@ // ugh! - size_t alignedStart = pane->alignFromReference(frame); - size_t alignedEnd = pane->alignFromReference(frame + duration); + sv_frame_t alignedStart = pane->alignFromReference(frame); + sv_frame_t alignedEnd = pane->alignFromReference(frame + duration); if (alignedStart >= alignedEnd) return; - size_t alignedDuration = alignedEnd - alignedStart; + sv_frame_t alignedDuration = alignedEnd - alignedStart; Command *c = 0; @@ -1000,7 +1160,7 @@ NoteModel *nm = dynamic_cast(layer->getModel()); if (nm) { NoteModel::Point point(alignedStart, - rm->getValueMinimum(), + nm->getValueMinimum(), alignedDuration, 1.f, ""); @@ -1015,6 +1175,25 @@ CommandHistory::getInstance()->addCommand(c, false); return; } + + FlexiNoteModel *fnm = dynamic_cast(layer->getModel()); + if (fnm) { + FlexiNoteModel::Point point(alignedStart, + fnm->getValueMinimum(), + alignedDuration, + 1.f, + ""); + FlexiNoteModel::EditCommand *command = + new FlexiNoteModel::EditCommand(fnm, tr("Add Point")); + command->addPoint(point); + command->setName(name); + c = command->finish(); + } + + if (c) { + CommandHistory::getInstance()->addCommand(c, false); + return; + } } void @@ -1044,7 +1223,7 @@ } MainWindowBase::FileOpenStatus -MainWindowBase::open(QString fileOrUrl, AudioFileOpenMode mode) +MainWindowBase::openPath(QString fileOrUrl, AudioFileOpenMode mode) { ProgressDialog dialog(tr("Opening file or URL..."), true, 2000, this); connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash())); @@ -1075,7 +1254,6 @@ RDFImporter::RDFDocumentType rdfType = RDFImporter::identifyDocumentType (QUrl::fromLocalFile(source.getLocalFilename()).toString()); -// cerr << "RDF type: " << (int)rdfType << endl; if (rdfType == RDFImporter::AudioRefAndAnnotations || rdfType == RDFImporter::AudioRef) { rdfSession = true; @@ -1128,31 +1306,44 @@ MainWindowBase::openAudio(FileSource source, AudioFileOpenMode mode, QString templateName) { -// SVDEBUG << "MainWindowBase::openAudio(" << source.getLocation() << ")" << endl; + SVDEBUG << "MainWindowBase::openAudio(" << source.getLocation() << ") with mode " << mode << " and template " << templateName << endl; if (templateName == "") { templateName = getDefaultSessionTemplate(); } - cerr << "template is: \"" << templateName << "\"" << endl; - - if (!source.isAvailable()) return FileOpenFailed; +// cerr << "template is: \"" << templateName << "\"" << endl; + + if (!source.isAvailable()) { + if (source.wasCancelled()) { + return FileOpenCancelled; + } else { + return FileOpenFailed; + } + } + source.waitForData(); m_openingAudioFile = true; - size_t rate = 0; - - if (Preferences::getInstance()->getResampleOnLoad()) { + sv_samplerate_t rate = 0; + + if (Preferences::getInstance()->getFixedSampleRate() != 0) { + rate = Preferences::getInstance()->getFixedSampleRate(); + } else if (Preferences::getInstance()->getResampleOnLoad()) { rate = m_playSource->getSourceSampleRate(); } - WaveFileModel *newModel = new WaveFileModel(source, rate); + ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(source, rate); if (!newModel->isOK()) { delete newModel; m_openingAudioFile = false; - return FileOpenFailed; + if (source.wasCancelled()) { + return FileOpenCancelled; + } else { + return FileOpenFailed; + } } // cerr << "mode = " << mode << endl; @@ -1223,6 +1414,7 @@ } if (mode == CreateAdditionalModel && !getMainModel()) { + SVDEBUG << "Mode is CreateAdditionalModel but we have no main model, switching to ReplaceSession mode" << endl; mode = ReplaceSession; } @@ -1232,7 +1424,7 @@ if (!checkSaveModified()) return FileOpenCancelled; - cerr << "SV looking for template " << templateName << endl; + SVDEBUG << "SV looking for template " << templateName << endl; if (templateName != "") { FileOpenStatus tplStatus = openSessionTemplate(templateName); if (tplStatus == FileOpenCancelled) { @@ -1246,10 +1438,12 @@ } if (!loadedTemplate) { + SVDEBUG << "No template found: closing session, creating new empty document" << endl; closeSession(); createDocument(); } + SVDEBUG << "Now switching to ReplaceMainModel mode" << endl; mode = ReplaceMainModel; } @@ -1363,6 +1557,8 @@ currentPaneChanged(m_paneStack->getCurrentPane()); + emit audioFileLoaded(); + return FileOpenSucceeded; } @@ -1592,7 +1788,7 @@ } MainWindowBase::FileOpenStatus -MainWindowBase::openSessionFile(QString fileOrUrl) +MainWindowBase::openSessionPath(QString fileOrUrl) { ProgressDialog dialog(tr("Opening session..."), true, 2000, this); connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash())); @@ -1607,7 +1803,10 @@ if (!source.isAvailable()) return FileOpenFailed; source.waitForData(); - if (source.getExtension().toLower() != "sv") { + QString sessionExt = + InteractiveFileFinder::getInstance()->getApplicationSessionExtension(); + + if (source.getExtension().toLower() != sessionExt) { RDFImporter::RDFDocumentType rdfType = RDFImporter::identifyDocumentType @@ -1638,7 +1837,7 @@ BZipFileDevice *bzFile = 0; QFile *rawFile = 0; - if (source.getExtension().toLower() == "sv") { + if (source.getExtension().toLower() == sessionExt) { bzFile = new BZipFileDevice(source.getLocalFilename()); if (!bzFile->open(QIODevice::ReadOnly)) { delete bzFile; @@ -1712,6 +1911,8 @@ source.getLocalFilename()); } + emit sessionLoaded(); + } else { setWindowTitle(QApplication::applicationName()); } @@ -1745,7 +1946,6 @@ QXmlInputSource *inputSource = 0; QFile *file = 0; - bool isTemplate = false; file = new QFile(source.getLocalFilename()); inputSource = new QXmlInputSource(file); @@ -1794,6 +1994,8 @@ CommandHistory::getInstance()->documentSaved(); m_documentModified = false; updateMenuStates(); + + emit sessionLoaded(); } return ok ? FileOpenSucceeded : FileOpenFailed; @@ -1825,13 +2027,15 @@ CommandHistory::getInstance()->documentSaved(); m_documentModified = false; + emit sessionLoaded(); + return status; } MainWindowBase::FileOpenStatus MainWindowBase::openLayersFromRDF(FileSource source) { - size_t rate = 0; + sv_samplerate_t rate = 0; SVDEBUG << "MainWindowBase::openLayersFromRDF" << endl; @@ -1977,28 +2181,44 @@ } void -MainWindowBase::createPlayTarget() +MainWindowBase::createAudioIO() { - if (m_playTarget) return; - + if (m_playTarget || m_audioIO) return; + + if (!(m_soundOptions & WithAudioOutput)) return; + + //!!! how to handle preferences +/* QSettings settings; settings.beginGroup("Preferences"); QString targetName = settings.value("audio-target", "").toString(); settings.endGroup(); - AudioTargetFactory *factory = AudioTargetFactory::getInstance(); factory->setDefaultCallbackTarget(targetName); - m_playTarget = factory->createCallbackTarget(m_playSource); - - if (!m_playTarget) { +*/ + + if (m_soundOptions & WithAudioInput) { + m_audioIO = breakfastquay::AudioFactory:: + createCallbackIO(m_recordTarget, m_playSource); + m_audioIO->suspend(); // start in suspended state + m_playSource->setSystemPlaybackTarget(m_audioIO); + } else { + m_playTarget = breakfastquay::AudioFactory:: + createCallbackPlayTarget(m_playSource); + m_playTarget->suspend(); // start in suspended state + m_playSource->setSystemPlaybackTarget(m_playTarget); + } + + if (!m_playTarget && !m_audioIO) { emit hideSplash(); - if (factory->isAutoCallbackTarget(targetName)) { +// if (factory->isAutoCallbackTarget(targetName)) { QMessageBox::warning (this, tr("Couldn't open audio device"), tr("No audio available

Could not open an audio device for playback.

Automatic audio device detection failed. Audio playback will not be available during this session.

"), QMessageBox::Ok); +/* } else { QMessageBox::warning (this, tr("Couldn't open audio device"), @@ -2006,6 +2226,8 @@ .arg(factory->getCallbackTargetDescription(targetName)), QMessageBox::Ok); } +*/ + return; } } @@ -2077,6 +2299,7 @@ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QTextStream out(&bzFile); + out.setCodec(QTextCodec::codecForName("UTF-8")); toXml(out, false); out.flush(); @@ -2122,6 +2345,7 @@ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QTextStream out(&file); + out.setCodec(QTextCodec::codecForName("UTF-8")); toXml(out, true); out.flush(); @@ -2179,6 +2403,7 @@ Pane * MainWindowBase::addPaneToStack() { + cerr << "MainWindowBase::addPaneToStack()" << endl; AddPaneCommand *command = new AddPaneCommand(this); CommandHistory::getInstance()->addCommand(command); Pane *pane = command->getPane(); @@ -2208,17 +2433,17 @@ Model *model = getMainModel(); if (!model) return; - size_t start = model->getStartFrame(); - size_t end = model->getEndFrame(); + sv_frame_t start = model->getStartFrame(); + sv_frame_t end = model->getEndFrame(); if (m_playSource) end = std::max(end, m_playSource->getPlayEndFrame()); - size_t pixels = currentPane->width(); - - size_t sw = currentPane->getVerticalScaleWidth(); + int pixels = currentPane->width(); + + int sw = currentPane->getVerticalScaleWidth(); if (pixels > sw * 2) pixels -= sw * 2; else pixels = 1; if (pixels > 4) pixels -= 4; - size_t zoomLevel = (end - start) / pixels; + int zoomLevel = int((end - start) / pixels); if (zoomLevel < 1) zoomLevel = 1; currentPane->setZoomLevel(zoomLevel); @@ -2229,7 +2454,11 @@ MainWindowBase::zoomDefault() { Pane *currentPane = m_paneStack->getCurrentPane(); - if (currentPane) currentPane->setZoomLevel(1024); + QSettings settings; + settings.beginGroup("MainWindow"); + int zoom = settings.value("zoom-default", 1024).toInt(); + settings.endGroup(); + if (currentPane) currentPane->setZoomLevel(zoom); } void @@ -2283,7 +2512,7 @@ void MainWindowBase::showMinimalOverlays() { - m_viewManager->setOverlayMode(ViewManager::MinimalOverlays); + m_viewManager->setOverlayMode(ViewManager::StandardOverlays); } void @@ -2358,6 +2587,22 @@ } } +QLabel * +MainWindowBase::getStatusLabel() const +{ + if (!m_statusLabel) { + m_statusLabel = new QLabel(); + statusBar()->addWidget(m_statusLabel, 1); + } + + QList frames = statusBar()->findChildren(); + foreach (QFrame *f, frames) { + f->setFrameStyle(QFrame::NoFrame); + } + + return m_statusLabel; +} + void MainWindowBase::toggleStatusBar() { @@ -2414,37 +2659,180 @@ void MainWindowBase::play() { - if (m_playSource->isPlaying()) { + if (m_recordTarget->isRecording() || m_playSource->isPlaying()) { stop(); + QAction *action = qobject_cast(sender()); + if (action) action->setChecked(false); } else { + if (m_audioIO) m_audioIO->resume(); + else if (m_playTarget) m_playTarget->resume(); playbackFrameChanged(m_viewManager->getPlaybackFrame()); m_playSource->play(m_viewManager->getPlaybackFrame()); } } void +MainWindowBase::record() +{ + if (!(m_soundOptions & WithAudioInput)) { + return; + } + + if (!m_recordTarget) { + //!!! report + return; + } + + if (!m_audioIO) { + createAudioIO(); + } + + if (!m_audioIO) { + //!!! report + return; + } + + if (m_recordTarget->isRecording()) { + stop(); + return; + } + + QAction *action = qobject_cast(sender()); + + if (m_audioRecordMode == RecordReplaceSession) { + if (!checkSaveModified()) { + if (action) action->setChecked(false); + return; + } + } + + m_audioIO->resume(); + + WritableWaveFileModel *model = m_recordTarget->startRecording(); + if (!model) { + cerr << "ERROR: MainWindowBase::record: Recording failed" << endl; + //!!! report + if (action) action->setChecked(false); + return; + } + + if (!model->isOK()) { + m_recordTarget->stopRecording(); + m_audioIO->suspend(); + delete model; + return; + } + + PlayParameterRepository::getInstance()->addPlayable(model); + + if (m_audioRecordMode == RecordReplaceSession || !getMainModel()) { + + //!!! duplication with openAudio here + + QString templateName = getDefaultSessionTemplate(); + bool loadedTemplate = false; + + if (templateName != "") { + FileOpenStatus tplStatus = openSessionTemplate(templateName); + if (tplStatus == FileOpenCancelled) { + m_recordTarget->stopRecording(); + m_audioIO->suspend(); + PlayParameterRepository::getInstance()->removePlayable(model); + return; + } + if (tplStatus != FileOpenFailed) { + loadedTemplate = true; + } + } + + if (!loadedTemplate) { + closeSession(); + createDocument(); + } + + Model *prevMain = getMainModel(); + if (prevMain) { + m_playSource->removeModel(prevMain); + PlayParameterRepository::getInstance()->removePlayable(prevMain); + } + + m_document->setMainModel(model); + setupMenus(); + + if (loadedTemplate || (m_sessionFile == "")) { + //!!! shouldn't be dealing directly with title from here -- call a method + setWindowTitle(tr("%1: %2") + .arg(QApplication::applicationName()) + .arg(model->getLocation())); + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + } else { + setWindowTitle(tr("%1: %2 [%3]") + .arg(QApplication::applicationName()) + .arg(QFileInfo(m_sessionFile).fileName()) + .arg(model->getLocation())); + if (m_documentModified) { + m_documentModified = false; + documentModified(); // so as to restore "(modified)" window title + } + } + + } else { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Import Recorded Audio"), true); + + m_document->addImportedModel(model); + + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + + Pane *pane = command->getPane(); + + if (m_timeRulerLayer) { + m_document->addLayerToView(pane, m_timeRulerLayer); + } + + Layer *newLayer = m_document->createImportedLayer(model); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + + updateMenuStates(); + m_recentFiles.addFile(model->getLocation()); + currentPaneChanged(m_paneStack->getCurrentPane()); + + emit audioFileLoaded(); +} + +void MainWindowBase::ffwd() { if (!getMainModel()) return; - int frame = m_viewManager->getPlaybackFrame(); + sv_frame_t frame = m_viewManager->getPlaybackFrame(); ++frame; Pane *pane = m_paneStack->getCurrentPane(); Layer *layer = getSnapLayer(); - size_t sr = getMainModel()->getSampleRate(); + sv_samplerate_t sr = getMainModel()->getSampleRate(); if (!layer) { frame = RealTime::realTime2Frame - (RealTime::frame2RealTime(frame, sr) + RealTime(2, 0), sr); - if (frame > int(getMainModel()->getEndFrame())) { + (RealTime::frame2RealTime(frame, sr) + m_defaultFfwdRwdStep, sr); + if (frame > getMainModel()->getEndFrame()) { frame = getMainModel()->getEndFrame(); } } else { - size_t resolution = 0; + int resolution = 0; if (pane) frame = pane->alignFromReference(frame); if (layer->snapToFeatureFrame(m_paneStack->getCurrentPane(), frame, resolution, Layer::SnapRight)) { @@ -2457,7 +2845,7 @@ if (frame < 0) frame = 0; if (m_viewManager->getPlaySelectionMode()) { - frame = m_viewManager->constrainFrameToSelection(size_t(frame)); + frame = m_viewManager->constrainFrameToSelection(frame); } m_viewManager->setPlaybackFrame(frame); @@ -2481,7 +2869,7 @@ stop(); } - size_t frame = getMainModel()->getEndFrame(); + sv_frame_t frame = getMainModel()->getEndFrame(); if (m_viewManager->getPlaySelectionMode()) { frame = m_viewManager->constrainFrameToSelection(frame); @@ -2499,11 +2887,9 @@ if (!layer) { ffwd(); return; } Pane *pane = m_paneStack->getCurrentPane(); - size_t sr = getMainModel()->getSampleRate(); - - int frame = m_viewManager->getPlaybackFrame(); - - size_t resolution = 0; + sv_frame_t frame = m_viewManager->getPlaybackFrame(); + + int resolution = 0; if (pane) frame = pane->alignFromReference(frame); if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(), frame, resolution, Layer::SnapRight)) { @@ -2515,7 +2901,7 @@ if (frame < 0) frame = 0; if (m_viewManager->getPlaySelectionMode()) { - frame = m_viewManager->constrainFrameToSelection(size_t(frame)); + frame = m_viewManager->constrainFrameToSelection(frame); } m_viewManager->setPlaybackFrame(frame); @@ -2533,19 +2919,19 @@ { if (!getMainModel()) return; - int frame = m_viewManager->getPlaybackFrame(); + sv_frame_t frame = m_viewManager->getPlaybackFrame(); if (frame > 0) --frame; Pane *pane = m_paneStack->getCurrentPane(); Layer *layer = getSnapLayer(); - size_t sr = getMainModel()->getSampleRate(); + sv_samplerate_t sr = getMainModel()->getSampleRate(); // when rewinding during playback, we want to allow a period // following a rewind target point at which the rewind will go to // the prior point instead of the immediately neighbouring one if (m_playSource && m_playSource->isPlaying()) { RealTime ct = RealTime::frame2RealTime(frame, sr); - ct = ct - RealTime::fromSeconds(0.25); + ct = ct - RealTime::fromSeconds(0.15); if (ct < RealTime::zeroTime) ct = RealTime::zeroTime; frame = RealTime::realTime2Frame(ct, sr); } @@ -2553,14 +2939,14 @@ if (!layer) { frame = RealTime::realTime2Frame - (RealTime::frame2RealTime(frame, sr) - RealTime(2, 0), sr); - if (frame < int(getMainModel()->getStartFrame())) { + (RealTime::frame2RealTime(frame, sr) - m_defaultFfwdRwdStep, sr); + if (frame < getMainModel()->getStartFrame()) { frame = getMainModel()->getStartFrame(); } } else { - size_t resolution = 0; + int resolution = 0; if (pane) frame = pane->alignFromReference(frame); if (layer->snapToFeatureFrame(m_paneStack->getCurrentPane(), frame, resolution, Layer::SnapLeft)) { @@ -2573,7 +2959,7 @@ if (frame < 0) frame = 0; if (m_viewManager->getPlaySelectionMode()) { - frame = m_viewManager->constrainFrameToSelection(size_t(frame)); + frame = m_viewManager->constrainFrameToSelection(frame); } m_viewManager->setPlaybackFrame(frame); @@ -2584,7 +2970,7 @@ { if (!getMainModel()) return; - size_t frame = getMainModel()->getStartFrame(); + sv_frame_t frame = getMainModel()->getStartFrame(); if (m_viewManager->getPlaySelectionMode()) { frame = m_viewManager->constrainFrameToSelection(frame); @@ -2602,11 +2988,9 @@ if (!layer) { rewind(); return; } Pane *pane = m_paneStack->getCurrentPane(); - size_t sr = getMainModel()->getSampleRate(); - - int frame = m_viewManager->getPlaybackFrame(); - - size_t resolution = 0; + sv_frame_t frame = m_viewManager->getPlaybackFrame(); + + int resolution = 0; if (pane) frame = pane->alignFromReference(frame); if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(), frame, resolution, Layer::SnapLeft)) { @@ -2618,7 +3002,7 @@ if (frame < 0) frame = 0; if (m_viewManager->getPlaySelectionMode()) { - frame = m_viewManager->constrainFrameToSelection(size_t(frame)); + frame = m_viewManager->constrainFrameToSelection(frame); } m_viewManager->setPlaybackFrame(frame); @@ -2654,13 +3038,20 @@ void MainWindowBase::stop() { + if (m_recordTarget->isRecording()) { + m_recordTarget->stopRecording(); + } + m_playSource->stop(); + if (m_audioIO) m_audioIO->suspend(); + else if (m_playTarget) m_playTarget->suspend(); + if (m_paneStack && m_paneStack->getCurrentPane()) { updateVisibleRangeDisplay(m_paneStack->getCurrentPane()); } else { m_myStatusMessage = ""; - statusBar()->showMessage(""); + getStatusLabel()->setText(""); } } @@ -2713,6 +3104,7 @@ MainWindowBase::RemovePaneCommand::RemovePaneCommand(MainWindowBase *mw, Pane *pane) : m_mw(mw), m_pane(pane), + m_prevCurrentPane(0), m_added(true) { } @@ -2830,24 +3222,24 @@ MainWindowBase::connectLayerEditDialog(ModelDataTableDialog *dialog) { connect(m_viewManager, - SIGNAL(globalCentreFrameChanged(unsigned long)), + SIGNAL(globalCentreFrameChanged(sv_frame_t)), dialog, - SLOT(userScrolledToFrame(unsigned long))); + SLOT(userScrolledToFrame(sv_frame_t))); connect(m_viewManager, - SIGNAL(playbackFrameChanged(unsigned long)), + SIGNAL(playbackFrameChanged(sv_frame_t)), dialog, - SLOT(playbackScrolledToFrame(unsigned long))); + SLOT(playbackScrolledToFrame(sv_frame_t))); connect(dialog, - SIGNAL(scrollToFrame(unsigned long)), + SIGNAL(scrollToFrame(sv_frame_t)), m_viewManager, - SLOT(setGlobalCentreFrame(unsigned long))); + SLOT(setGlobalCentreFrame(sv_frame_t))); connect(dialog, - SIGNAL(scrollToFrame(unsigned long)), + SIGNAL(scrollToFrame(sv_frame_t)), m_viewManager, - SLOT(setPlaybackFrame(unsigned long))); + SLOT(setPlaybackFrame(sv_frame_t))); } void @@ -2889,53 +3281,75 @@ void MainWindowBase::previousLayer() { - //!!! Not right -- pane lists layers in stacking order - if (!m_paneStack) return; Pane *currentPane = m_paneStack->getCurrentPane(); if (!currentPane) return; + int count = currentPane->getLayerCount(); + if (count == 0) return; + Layer *currentLayer = currentPane->getSelectedLayer(); - if (!currentLayer) return; - - for (int i = 0; i < currentPane->getLayerCount(); ++i) { - if (currentPane->getLayer(i) == currentLayer) { - if (i == 0) return; - m_paneStack->setCurrentLayer(currentPane, - currentPane->getLayer(i-1)); - updateMenuStates(); - return; + + if (!currentLayer) { + // The pane itself is current + m_paneStack->setCurrentLayer + (currentPane, currentPane->getFixedOrderLayer(count-1)); + } else { + for (int i = 0; i < count; ++i) { + if (currentPane->getFixedOrderLayer(i) == currentLayer) { + if (i == 0) { + m_paneStack->setCurrentLayer + (currentPane, 0); // pane + } else { + m_paneStack->setCurrentLayer + (currentPane, currentPane->getFixedOrderLayer(i-1)); + } + break; + } } } + + updateMenuStates(); } void MainWindowBase::nextLayer() { - //!!! Not right -- pane lists layers in stacking order - if (!m_paneStack) return; Pane *currentPane = m_paneStack->getCurrentPane(); if (!currentPane) return; + int count = currentPane->getLayerCount(); + if (count == 0) return; + Layer *currentLayer = currentPane->getSelectedLayer(); - if (!currentLayer) return; - - for (int i = 0; i < currentPane->getLayerCount(); ++i) { - if (currentPane->getLayer(i) == currentLayer) { - if (i == currentPane->getLayerCount()-1) return; - m_paneStack->setCurrentLayer(currentPane, - currentPane->getLayer(i+1)); - updateMenuStates(); - return; + + if (!currentLayer) { + // The pane itself is current + m_paneStack->setCurrentLayer + (currentPane, currentPane->getFixedOrderLayer(0)); + } else { + for (int i = 0; i < count; ++i) { + if (currentPane->getFixedOrderLayer(i) == currentLayer) { + if (i == currentPane->getLayerCount()-1) { + m_paneStack->setCurrentLayer + (currentPane, 0); // pane + } else { + m_paneStack->setCurrentLayer + (currentPane, currentPane->getFixedOrderLayer(i+1)); + } + break; + } } } + + updateMenuStates(); } void -MainWindowBase::playbackFrameChanged(unsigned long frame) +MainWindowBase::playbackFrameChanged(sv_frame_t frame) { if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; @@ -2967,11 +3381,22 @@ m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)") .arg(nowStr).arg(thenStr).arg(remainingStr); - statusBar()->showMessage(m_myStatusMessage); + getStatusLabel()->setText(m_myStatusMessage); } void -MainWindowBase::globalCentreFrameChanged(unsigned long ) +MainWindowBase::recordDurationChanged(sv_frame_t frame, sv_samplerate_t rate) +{ + RealTime duration = RealTime::frame2RealTime(frame, rate); + QString durStr = duration.toSecText().c_str(); + + m_myStatusMessage = tr("Recording: %1").arg(durStr); + + getStatusLabel()->setText(m_myStatusMessage); +} + +void +MainWindowBase::globalCentreFrameChanged(sv_frame_t ) { if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; Pane *p = 0; @@ -2981,7 +3406,7 @@ } void -MainWindowBase::viewCentreFrameChanged(View *v, unsigned long frame) +MainWindowBase::viewCentreFrameChanged(View *v, sv_frame_t frame) { // SVDEBUG << "MainWindowBase::viewCentreFrameChanged(" << v << "," << frame << ")" << endl; @@ -2998,7 +3423,7 @@ } void -MainWindowBase::viewZoomLevelChanged(View *v, unsigned long , bool ) +MainWindowBase::viewZoomLevelChanged(View *v, int , bool ) { if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; Pane *p = 0; @@ -3091,6 +3516,7 @@ MainWindowBase::modelAdded(Model *model) { // SVDEBUG << "MainWindowBase::modelAdded(" << model << ")" << endl; + std::cerr << "\nAdding model " << model->getTypeName() << " to playsource " << std::endl; m_playSource->addModel(model); } @@ -3100,7 +3526,10 @@ // SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl; updateDescriptionLabel(); if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); - if (model && !m_playTarget && m_audioOutput) createPlayTarget(); + if (model && !(m_playTarget || m_audioIO) && + (m_soundOptions & WithAudioOutput)) { + createAudioIO(); + } } void @@ -3111,7 +3540,6 @@ m_viewManager->setPlaybackModel(0); } m_playSource->removeModel(model); - FFTDataServer::modelAboutToBeDeleted(model); } void @@ -3172,17 +3600,25 @@ { Pane *currentPane = 0; if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); - if (currentPane) updateVisibleRangeDisplay(currentPane); + if (currentPane) { + //cerr << "JTEST: mouse event on selection pane" << endl; + updateVisibleRangeDisplay(currentPane); + } } void MainWindowBase::contextHelpChanged(const QString &s) { + QLabel *lab = getStatusLabel(); + if (s == "" && m_myStatusMessage != "") { - statusBar()->showMessage(m_myStatusMessage); + if (lab->text() != m_myStatusMessage) { + lab->setText(m_myStatusMessage); + } return; } - statusBar()->showMessage(s); + + lab->setText(s); } void @@ -3200,15 +3636,12 @@ process->start("open", args); #else #ifdef Q_OS_WIN32 - - QString pf(getenv("ProgramFiles")); - QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); - - args.append(url); - process->start(command, args); - + QString pf(getenv("ProgramFiles")); + QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); + + args.append(url); + process->start(command, args); #else -#ifdef Q_WS_X11 if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { args.append("exec"); args.append(url); @@ -3222,7 +3655,32 @@ } #endif #endif +} + +void +MainWindowBase::openLocalFolder(QString path) +{ + QDir d(path); + if (d.exists()) { + QStringList args; + QString path = d.canonicalPath(); +#if defined Q_OS_WIN32 + // Although the Win32 API is quite happy to have + // forward slashes as directory separators, Windows + // Explorer is not + path = path.replace('/', '\\'); + args << path; + QProcess::execute("c:/windows/explorer.exe", args); +#else + args << path; + QProcess::execute( +#if defined Q_OS_MAC + "/usr/bin/open", +#else + "/usr/bin/xdg-open", #endif + args); +#endif + } } - diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/MainWindowBase.h --- a/framework/MainWindowBase.h Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/MainWindowBase.h Wed Apr 20 12:06:28 2016 +0100 @@ -46,7 +46,7 @@ class WaveformLayer; class WaveFileModel; class AudioCallbackPlaySource; -class AudioCallbackPlayTarget; +class AudioRecordTarget; class CommandHistory; class QMenu; class AudioDial; @@ -60,6 +60,13 @@ class KeyReference; class Labeller; class ModelDataTableDialog; +class QSignalMapper; +class QShortcut; + +namespace breakfastquay { +class SystemPlaybackTarget; +class SystemAudioIO; +} /** * The base class for the SV main window. This includes everything to @@ -75,7 +82,15 @@ Q_OBJECT public: - MainWindowBase(bool withAudioOutput, bool withOSCSupport, bool withMIDIInput); + enum SoundOption { + WithAudioOutput = 0x01, + WithAudioInput = 0x02, + WithMIDIInput = 0x04, + WithEverything = 0xff + }; + typedef int SoundOptions; + + MainWindowBase(SoundOptions options = WithEverything); virtual ~MainWindowBase(); enum AudioFileOpenMode { @@ -93,16 +108,20 @@ FileOpenWrongMode // attempted to open layer when no main model present }; - virtual FileOpenStatus open(QString fileOrUrl, AudioFileOpenMode = AskUser); + enum AudioRecordMode { + RecordReplaceSession, + RecordCreateAdditionalModel + }; + virtual FileOpenStatus open(FileSource source, AudioFileOpenMode = AskUser); - + virtual FileOpenStatus openPath(QString fileOrUrl, AudioFileOpenMode = AskUser); virtual FileOpenStatus openAudio(FileSource source, AudioFileOpenMode = AskUser, QString templateName = ""); virtual FileOpenStatus openPlaylist(FileSource source, AudioFileOpenMode = AskUser); virtual FileOpenStatus openLayer(FileSource source); virtual FileOpenStatus openImage(FileSource source); - virtual FileOpenStatus openSessionFile(QString fileOrUrl); virtual FileOpenStatus openSession(FileSource source); + virtual FileOpenStatus openSessionPath(QString fileOrUrl); virtual FileOpenStatus openSessionTemplate(QString templateName); virtual FileOpenStatus openSessionTemplate(FileSource source); @@ -110,8 +129,16 @@ virtual bool saveSessionTemplate(QString path); /// Implementation of FrameTimer interface method - virtual unsigned long getFrame() const; + virtual sv_frame_t getFrame() const; + void setDefaultFfwdRwdStep(RealTime step) { + m_defaultFfwdRwdStep = step; + } + + void setAudioRecordMode(AudioRecordMode mode) { + m_audioRecordMode = mode; + } + signals: // Used to toggle the availability of menu actions void canAddPane(bool); @@ -141,6 +168,7 @@ void canZoom(bool); void canScroll(bool); void canPlay(bool); + void canRecord(bool); void canFfwd(bool); void canRewind(bool); void canPlaySelection(bool); @@ -152,7 +180,11 @@ void canSelectPreviousLayer(bool); void canSelectNextLayer(bool); void canSave(bool); + void canSaveAs(bool); void hideSplash(); + void hideSplash(QWidget *); + void sessionLoaded(); + void audioFileLoaded(); void replacedDocument(); void activity(QString); @@ -187,6 +219,7 @@ virtual void ffwdEnd(); virtual void rewind(); virtual void rewindStart(); + virtual void record(); virtual void stop(); virtual void ffwdSimilar(); @@ -205,15 +238,16 @@ virtual void playSelectionToggled(); virtual void playSoloToggled(); - virtual void sampleRateMismatch(size_t, size_t, bool) = 0; + virtual void sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool) = 0; virtual void audioOverloadPluginDisabled() = 0; virtual void audioTimeStretchMultiChannelDisabled() = 0; - virtual void playbackFrameChanged(unsigned long); - virtual void globalCentreFrameChanged(unsigned long); - virtual void viewCentreFrameChanged(View *, unsigned long); - virtual void viewZoomLevelChanged(View *, unsigned long, bool); + virtual void playbackFrameChanged(sv_frame_t); + virtual void globalCentreFrameChanged(sv_frame_t); + virtual void viewCentreFrameChanged(View *, sv_frame_t); + virtual void viewZoomLevelChanged(View *, int, bool); virtual void outputLevelsChanged(float, float) = 0; + virtual void recordDurationChanged(sv_frame_t, sv_samplerate_t); virtual void currentPaneChanged(Pane *); virtual void currentLayerChanged(Pane *, Layer *); @@ -228,14 +262,14 @@ virtual void copy(); virtual void paste(); virtual void pasteAtPlaybackPosition(); - virtual void pasteRelative(int offset); + virtual void pasteRelative(sv_frame_t offset); virtual void deleteSelected(); virtual void insertInstant(); - virtual void insertInstantAt(size_t); + virtual void insertInstantAt(sv_frame_t); virtual void insertInstantsAtBoundaries(); virtual void insertItemAtSelection(); - virtual void insertItemAt(size_t, size_t); + virtual void insertItemAt(sv_frame_t, sv_frame_t); virtual void renumberInstants(); virtual void documentModified(); @@ -280,8 +314,12 @@ virtual void closeSession() = 0; + virtual void emitHideSplash(); + virtual void newerVersionAvailable(QString) { } + virtual void menuActionMapperInvoked(QObject *); + protected: QString m_sessionFile; QString m_audioFile; @@ -291,9 +329,12 @@ ViewManager *m_viewManager; Layer *m_timeRulerLayer; - bool m_audioOutput; + SoundOptions m_soundOptions; + AudioCallbackPlaySource *m_playSource; - AudioCallbackPlayTarget *m_playTarget; + AudioRecordTarget *m_recordTarget; + breakfastquay::SystemPlaybackTarget *m_playTarget; // only one of this... + breakfastquay::SystemAudioIO *m_audioIO; // ... and this exists class OSCQueueStarter : public QThread { @@ -309,6 +350,7 @@ OSCQueue *m_oscQueue; OSCQueueStarter *m_oscQueueStarter; + void startOSCQueue(); MIDIInput *m_midiInput; @@ -326,6 +368,13 @@ bool m_initialDarkBackground; + RealTime m_defaultFfwdRwdStep; + + AudioRecordMode m_audioRecordMode; + + mutable QLabel *m_statusLabel; + QLabel *getStatusLabel() const; + WaveFileModel *getMainModel(); const WaveFileModel *getMainModel() const; void createDocument(); @@ -350,8 +399,8 @@ virtual void setWindowSize(int width, int height) { m_mw->resizeConstrained(QSize(width, height)); } - virtual void addSelection(int start, int end) { - m_mw->m_viewManager->addSelection(Selection(start, end)); + virtual void addSelection(sv_frame_t start, sv_frame_t end) { + m_mw->m_viewManager->addSelectionQuietly(Selection(start, end)); } protected: MainWindowBase *m_mw; @@ -402,13 +451,23 @@ virtual QString getDefaultSessionTemplate() const; virtual void setDefaultSessionTemplate(QString); - virtual void createPlayTarget(); + virtual void createAudioIO(); virtual void openHelpUrl(QString url); + virtual void openLocalFolder(QString path); virtual void setupMenus() = 0; virtual void updateVisibleRangeDisplay(Pane *p) const = 0; virtual void updatePositionStatusDisplays() const = 0; + // Call this after setting up the menu bar, to fix up single-key + // shortcuts on OS/X + virtual void finaliseMenus(); + virtual void finaliseMenu(QMenu *); + + // Only used on OS/X to work around a Qt/Cocoa bug, see finaliseMenus + QSignalMapper *m_menuShortcutMapper; + QList m_appShortcuts; + virtual bool shouldCreateNewSessionForRDFAudio(bool *) { return true; } virtual void connectLayerEditDialog(ModelDataTableDialog *dialog); diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/SVFileReader.cpp --- a/framework/SVFileReader.cpp Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/SVFileReader.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -26,11 +26,12 @@ #include "data/fileio/FileFinder.h" -#include "data/model/WaveFileModel.h" +#include "data/model/ReadOnlyWaveFileModel.h" #include "data/model/EditableDenseThreeDimensionalModel.h" #include "data/model/SparseOneDimensionalModel.h" #include "data/model/SparseTimeValueModel.h" #include "data/model/NoteModel.h" +#include "data/model/FlexiNoteModel.h" #include "data/model/RegionModel.h" #include "data/model/TextModel.h" #include "data/model/ImageModel.h" @@ -57,15 +58,20 @@ m_paneCallback(callback), m_location(location), m_currentPane(0), + m_currentLayer(0), m_currentDataset(0), m_currentDerivedModel(0), m_currentDerivedModelId(-1), m_currentPlayParameters(0), m_currentTransformSource(0), + m_currentTransformChannel(0), + m_currentTransformIsNewStyle(true), m_datasetSeparator(" "), m_inRow(false), m_inLayer(false), m_inView(false), + m_inData(false), + m_inSelections(false), m_rowNumber(0), m_ok(false) { @@ -299,7 +305,7 @@ } else if (name == "derivation") { if (!m_currentDerivedModel) { - if (m_currentDerivedModel < 0) { + if (m_currentDerivedModelId < 0) { cerr << "WARNING: SV-XML: Bad derivation output model id " << m_currentDerivedModelId << endl; } else if (haveModel(m_currentDerivedModelId)) { @@ -325,7 +331,7 @@ } } } else { - m_document->addDerivedModel + m_document->addAlreadyDerivedModel (m_currentTransform, ModelTransformer::Input(m_currentTransformSource, m_currentTransformChannel), @@ -444,10 +450,10 @@ SVDEBUG << "SVFileReader::readModel: model name \"" << name << "\"" << endl; - READ_MANDATORY(int, sampleRate, toInt); + READ_MANDATORY(double, sampleRate, toDouble); QString type = attributes.value("type").trimmed(); - bool mainModel = (attributes.value("mainModel").trimmed() == "true"); + bool isMainModel = (attributes.value("mainModel").trimmed() == "true"); if (type == "wavefile") { @@ -472,15 +478,18 @@ file.waitForData(); - size_t rate = 0; + sv_samplerate_t rate = sampleRate; - if (!mainModel && - Preferences::getInstance()->getResampleOnLoad()) { + if (Preferences::getInstance()->getFixedSampleRate() != 0) { + rate = Preferences::getInstance()->getFixedSampleRate(); + } else if (rate == 0 && + !isMainModel && + Preferences::getInstance()->getResampleOnLoad()) { WaveFileModel *mm = m_document->getMainModel(); if (mm) rate = mm->getSampleRate(); } - model = new WaveFileModel(file, rate); + model = new ReadOnlyWaveFileModel(file, rate); if (!model->isOK()) { delete model; model = 0; @@ -491,7 +500,7 @@ model->setObjectName(name); m_models[id] = model; - if (mainModel) { + if (isMainModel) { m_document->setMainModel(model); m_addedModels.insert(model); } @@ -624,6 +633,19 @@ model->setScaleUnits(units); model->setObjectName(name); m_models[id] = model; + } else if (attributes.value("subtype") == "flexinote") { + FlexiNoteModel *model; + if (haveMinMax) { + model = new FlexiNoteModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + } else { + model = new FlexiNoteModel + (sampleRate, resolution, notifyOnAdd); + } + model->setValueQuantization(valueQuantization); + model->setScaleUnits(units); + model->setObjectName(name); + m_models[id] = model; } else { // note models written out by SV 1.3 and earlier // have no subtype, so we can't test that @@ -726,6 +748,8 @@ m_currentPane = m_paneCallback.addPane(); + cerr << "SVFileReader::addPane: pane is " << m_currentPane << endl; + if (!m_currentPane) { cerr << "WARNING: SV-XML: Internal error: Failed to add pane!" << endl; @@ -738,8 +762,8 @@ // The view properties first - READ_MANDATORY(size_t, centre, toUInt); - READ_MANDATORY(size_t, zoom, toUInt); + READ_MANDATORY(int, centre, toInt); + READ_MANDATORY(int, zoom, toInt); READ_MANDATORY(int, followPan, toInt); READ_MANDATORY(int, followZoom, toInt); QString tracking = attributes.value("tracking"); @@ -748,7 +772,8 @@ view->setFollowGlobalPan(followPan); view->setFollowGlobalZoom(followZoom); view->setPlaybackFollow(tracking == "scroll" ? PlaybackScrollContinuous : - tracking == "page" ? PlaybackScrollPage + tracking == "page" ? PlaybackScrollPageWithCentre : + tracking == "daw" ? PlaybackScrollPage : PlaybackIgnore); // Then set these values @@ -861,13 +886,20 @@ } else { cerr << "WARNING: SV-XML: Unknown model id " << modelId << " in layer definition" << endl; - } + if (!layer->canExistWithoutModel()) { + // Don't add a layer with an unknown model id + // unless it explicitly supports this state + m_document->deleteLayer(layer); + m_layers[id] = layer = 0; + return false; + } + } } - layer->setProperties(attributes); + if (layer) layer->setProperties(attributes); } - if (!m_inData && m_currentPane) { + if (!m_inData && m_currentPane && layer) { QString visible = attributes.value("visible"); bool dormant = (visible == "false"); @@ -887,7 +919,7 @@ } m_currentLayer = layer; - m_inLayer = true; + m_inLayer = (layer != 0); return true; } @@ -932,6 +964,7 @@ case 3: if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) good = true; else if (dynamic_cast(model)) good = true; else if (dynamic_cast(model)) { m_datasetSeparator = attributes.value("separator"); @@ -987,8 +1020,8 @@ // cerr << "Current dataset is a note model" << endl; float value = 0.0; value = attributes.value("value").trimmed().toFloat(&ok); - size_t duration = 0; - duration = attributes.value("duration").trimmed().toUInt(&ok); + int duration = 0; + duration = attributes.value("duration").trimmed().toInt(&ok); QString label = attributes.value("label"); float level = attributes.value("level").trimmed().toFloat(&ok); if (!ok) { // level is optional @@ -999,14 +1032,32 @@ return ok; } + FlexiNoteModel *fnm = dynamic_cast(m_currentDataset); + + if (fnm) { +// cerr << "Current dataset is a flexinote model" << endl; + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + int duration = 0; + duration = attributes.value("duration").trimmed().toInt(&ok); + QString label = attributes.value("label"); + float level = attributes.value("level").trimmed().toFloat(&ok); + if (!ok) { // level is optional + level = 1.f; + ok = true; + } + fnm->addPoint(FlexiNoteModel::Point(frame, value, duration, level, label)); + return ok; + } + RegionModel *rm = dynamic_cast(m_currentDataset); if (rm) { -// cerr << "Current dataset is a note model" << endl; +// cerr << "Current dataset is a region model" << endl; float value = 0.0; value = attributes.value("value").trimmed().toFloat(&ok); - size_t duration = 0; - duration = attributes.value("duration").trimmed().toUInt(&ok); + int duration = 0; + duration = attributes.value("duration").trimmed().toInt(&ok); QString label = attributes.value("label"); rm->addPoint(RegionModel::Point(frame, value, duration, label)); return ok; @@ -1115,7 +1166,7 @@ for (QStringList::iterator i = data.begin(); i != data.end(); ++i) { - if (values.size() == dtdm->getHeight()) { + if (int(values.size()) == dtdm->getHeight()) { if (!warned) { cerr << "WARNING: SV-XML: Too many y-bins in 3-D dataset row " << m_rowNumber << endl; @@ -1210,8 +1261,8 @@ QString startFrameStr = attributes.value("startFrame"); QString durationStr = attributes.value("duration"); - size_t startFrame = 0; - size_t duration = 0; + int startFrame = 0; + int duration = 0; if (startFrameStr != "") { startFrame = startFrameStr.trimmed().toInt(&ok); @@ -1271,8 +1322,8 @@ float gain = attributes.value("gain").toFloat(&ok); if (ok) parameters->setPlayGain(gain); - QString pluginId = attributes.value("pluginId"); - if (pluginId != "") parameters->setPlayPluginId(pluginId); + QString clipId = attributes.value("clipId"); + if (clipId != "") parameters->setPlayClipId(clipId); m_currentPlayParameters = parameters; @@ -1291,17 +1342,26 @@ bool SVFileReader::readPlugin(const QXmlAttributes &attributes) { - if (m_currentDerivedModelId < 0 && !m_currentPlayParameters) { + if (m_currentDerivedModelId >= 0) { + return readPluginForTransform(attributes); + } else if (m_currentPlayParameters) { + return readPluginForPlayback(attributes); + } else { cerr << "WARNING: SV-XML: Plugin found outside derivation or play parameters" << endl; return false; } +} - if (!m_currentPlayParameters && m_currentTransformIsNewStyle) { +bool +SVFileReader::readPluginForTransform(const QXmlAttributes &attributes) +{ + if (m_currentTransformIsNewStyle) { + // Not needed, we have the transform element instead return true; } QString configurationXml = "setPlayPluginConfiguration(configurationXml); - } else { - TransformFactory::getInstance()-> - setParametersFromPluginConfigurationXml(m_currentTransform, - configurationXml); + TransformFactory::getInstance()-> + setParametersFromPluginConfigurationXml(m_currentTransform, + configurationXml); + return true; +} + +bool +SVFileReader::readPluginForPlayback(const QXmlAttributes &attributes) +{ + // Obsolete but supported for compatibility + + QString ident = attributes.value("identifier"); + if (ident == "sample_player") { + QString clipId = attributes.value("program"); + if (clipId != "") m_currentPlayParameters->setPlayClipId(clipId); } return true; diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/SVFileReader.h --- a/framework/SVFileReader.h Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/SVFileReader.h Wed Apr 20 12:06:28 2016 +0100 @@ -34,7 +34,7 @@ virtual ~SVFileReaderPaneCallback(); virtual Pane *addPane() = 0; virtual void setWindowSize(int width, int height) = 0; - virtual void addSelection(int start, int end) = 0; + virtual void addSelection(sv_frame_t start, sv_frame_t end) = 0; }; /** @@ -222,6 +222,8 @@ bool readDerivation(const QXmlAttributes &); bool readPlayParameters(const QXmlAttributes &); bool readPlugin(const QXmlAttributes &); + bool readPluginForTransform(const QXmlAttributes &); + bool readPluginForPlayback(const QXmlAttributes &); bool readTransform(const QXmlAttributes &); bool readParameter(const QXmlAttributes &); bool readSelection(const QXmlAttributes &); diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/TransformUserConfigurator.cpp --- a/framework/TransformUserConfigurator.cpp Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/TransformUserConfigurator.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -30,6 +30,14 @@ #include +static QWidget *parentWidget = 0; + +void +TransformUserConfigurator::setParentWidget(QWidget *w) +{ + parentWidget = w; +} + bool TransformUserConfigurator::getChannelRange(TransformId identifier, Vamp::PluginBase *plugin, @@ -38,8 +46,8 @@ if (plugin && plugin->getType() == "Feature Extraction Plugin") { Vamp::Plugin *vp = static_cast(plugin); SVDEBUG << "TransformUserConfigurator::getChannelRange: is a VP" << endl; - minChannels = vp->getMinChannelCount(); - maxChannels = vp->getMaxChannelCount(); + minChannels = int(vp->getMinChannelCount()); + maxChannels = int(vp->getMaxChannelCount()); return true; } else { SVDEBUG << "TransformUserConfigurator::getChannelRange: is not a VP" << endl; @@ -54,8 +62,8 @@ Vamp::PluginBase *plugin, Model *&inputModel, AudioPlaySource *source, - size_t startFrame, - size_t duration, + sv_frame_t startFrame, + sv_frame_t duration, const QMap &modelMap, QStringList candidateModelNames, QString defaultModelName) @@ -142,7 +150,8 @@ int defaultChannel = -1; //!!! no longer saved! [was context.channel] - PluginParameterDialog *dialog = new PluginParameterDialog(plugin); + PluginParameterDialog *dialog = new PluginParameterDialog + (plugin, parentWidget); dialog->setMoreInfoUrl(TransformFactory::getInstance()-> getTransformInfoUrl(transform.getIdentifier())); @@ -201,7 +210,7 @@ } } - size_t stepSize = 0, blockSize = 0; + int stepSize = 0, blockSize = 0; WindowType windowType = HanningWindow; dialog->getProcessingParameters(stepSize, diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/TransformUserConfigurator.h --- a/framework/TransformUserConfigurator.h Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/TransformUserConfigurator.h Wed Apr 20 12:06:28 2016 +0100 @@ -27,12 +27,14 @@ Vamp::PluginBase *plugin, Model *&inputModel, AudioPlaySource *source, - size_t startFrame, - size_t duration, + sv_frame_t startFrame, + sv_frame_t duration, const QMap &modelMap, QStringList candidateModelNames, QString defaultModelName); + static void setParentWidget(QWidget *); + private: bool getChannelRange(TransformId identifier, Vamp::PluginBase *plugin, int &min, int &max); diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/VersionTester.cpp --- a/framework/VersionTester.cpp Tue Jul 14 15:04:45 2015 +0100 +++ b/framework/VersionTester.cpp Wed Apr 20 12:06:28 2016 +0100 @@ -60,17 +60,21 @@ int be = blist.size(); int e = std::max(ae, be); for (int i = 0; i < e; ++i) { - int an = 0, bn = 0; - if (i < ae) { - an = alist[i].toInt(); - if (an == 0) an = -1; // non-numeric field -> "-pre1" etc - } - if (i < be) { - bn = blist[i].toInt(); - if (bn == 0) bn = -1; - } - if (an < bn) return false; - if (an > bn) return true; + int an = 0, bn = 0; + if (i < ae) { + an = alist[i].toInt(); + if (an == 0 && alist[i] != "0") { + an = -1; // non-numeric field -> "-pre1" etc + } + } + if (i < be) { + bn = blist[i].toInt(); + if (bn == 0 && blist[i] != "0") { + bn = -1; + } + } + if (an < bn) return false; + if (an > bn) return true; } return false; } @@ -103,8 +107,9 @@ if (lines.empty()) return; QString latestVersion = lines[0]; - SVDEBUG << "Comparing current version \"" << m_myVersion << "\" with latest version \"" << latestVersion << "\"" << endl; + cerr << "Comparing current version \"" << m_myVersion << "\" with latest version \"" << latestVersion << "\"" << endl; if (isVersionNewerThan(latestVersion, m_myVersion)) { + cerr << "Latest version \"" << latestVersion << "\" is newer than current version \"" << m_myVersion << "\"" << endl; emit newerVersionAvailable(latestVersion); } } diff -r 428ce32a8dd9 -r a2a8fa0eed08 framework/framework.pro --- a/framework/framework.pro Tue Jul 14 15:04:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -TEMPLATE = lib - -SV_UNIT_PACKAGES = vamp vamp-hostsdk # required because we use transform headers -load(../prf/sv.prf) - -CONFIG += sv staticlib qt thread warn_on stl rtti exceptions -QT += xml network - -TARGET = svframework - -DEPENDPATH += .. -INCLUDEPATH += . .. -OBJECTS_DIR = tmp_obj -MOC_DIR = tmp_moc - -HEADERS += Document.h \ - MainWindowBase.h \ - SVFileReader.h \ - VersionTester.h - -SOURCES += Document.cpp \ - MainWindowBase.cpp \ - SVFileReader.cpp \ - VersionTester.cpp - diff -r 428ce32a8dd9 -r a2a8fa0eed08 svapp.pro --- a/svapp.pro Tue Jul 14 15:04:45 2015 +0100 +++ b/svapp.pro Wed Apr 20 12:06:28 2016 +0100 @@ -1,50 +1,61 @@ TEMPLATE = lib +INCLUDEPATH += ../vamp-plugin-sdk +DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK + exists(config.pri) { include(config.pri) } -win* { - !exists(config.pri) { - DEFINES += HAVE_PORTAUDIO_2_0 +!exists(config.pri) { + + CONFIG += release + DEFINES += NDEBUG BUILD_RELEASE NO_TIMING + + win32-g++ { + INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include + LIBS += -L../sv-dependency-builds/win32-mingw/lib + } + win32-msvc* { + INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include + LIBS += -L../sv-dependency-builds/win32-msvc/lib + } + macx* { + INCLUDEPATH += ../sv-dependency-builds/osx/include + LIBS += -L../sv-dependency-builds/osx/lib + } + + win* { + DEFINES += HAVE_PORTAUDIO + } + macx* { + DEFINES += HAVE_COREAUDIO HAVE_PORTAUDIO } } -CONFIG += staticlib qt thread warn_on stl rtti exceptions +CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11 QT += network xml gui widgets TARGET = svapp -DEPENDPATH += . ../svcore ../svgui -INCLUDEPATH += . ../svcore ../svgui +DEPENDPATH += . ../bqaudioio ../svcore ../svgui +INCLUDEPATH += . ../bqaudioio ../svcore ../svgui OBJECTS_DIR = o MOC_DIR = o -win32-g++ { - INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include -} -win32-msvc* { - INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include -} +HEADERS += audio/AudioCallbackPlaySource.h \ + audio/AudioRecordTarget.h \ + audio/AudioGenerator.h \ + audio/ClipMixer.h \ + audio/ContinuousSynth.h \ + audio/PlaySpeedRangeMapper.h -HEADERS += audioio/AudioCallbackPlaySource.h \ - audioio/AudioCallbackPlayTarget.h \ - audioio/AudioCoreAudioTarget.h \ - audioio/AudioGenerator.h \ - audioio/AudioJACKTarget.h \ - audioio/AudioPortAudioTarget.h \ - audioio/AudioPulseAudioTarget.h \ - audioio/AudioTargetFactory.h \ - audioio/PlaySpeedRangeMapper.h -SOURCES += audioio/AudioCallbackPlaySource.cpp \ - audioio/AudioCallbackPlayTarget.cpp \ - audioio/AudioCoreAudioTarget.cpp \ - audioio/AudioGenerator.cpp \ - audioio/AudioJACKTarget.cpp \ - audioio/AudioPortAudioTarget.cpp \ - audioio/AudioPulseAudioTarget.cpp \ - audioio/AudioTargetFactory.cpp \ - audioio/PlaySpeedRangeMapper.cpp +SOURCES += audio/AudioCallbackPlaySource.cpp \ + audio/AudioRecordTarget.cpp \ + audio/AudioGenerator.cpp \ + audio/ClipMixer.cpp \ + audio/ContinuousSynth.cpp \ + audio/PlaySpeedRangeMapper.cpp HEADERS += framework/Document.h \ framework/MainWindowBase.h \