Mercurial > hg > svapp
changeset 519:a2a8fa0eed08 3.0-plus-imaf
Merge branches 3.0-integration and imaf_enc to 3.0-plus-imaf
author | Chris Cannam |
---|---|
date | Wed, 20 Apr 2016 12:06:28 +0100 |
parents | f7ec9e410108 (diff) 428ce32a8dd9 (current diff) |
children | |
files | framework/MainWindowBase.cpp |
diffstat | 45 files changed, 5984 insertions(+), 6082 deletions(-) [+] |
line wrap: on
line diff
--- 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 <bkoz@redhat.com> +# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> +# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> +# Copyright (c) 2014 Alexey Sokolov <sokolov@google.com> +# +# 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 <typename T> + 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<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(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 +]) +
--- /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 <rubberband/RubberBandStretcher.h> +using namespace RubberBand; + +#include <iostream> +#include <cassert> + +//#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<ReadOnlyWaveFileModel *>(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<Model *>::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<ReadOnlyWaveFileModel *>(*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<Model *>::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<float>(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<float> *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<float> *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<RealTimePluginInstance *>(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<Model *> 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<float> *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<float> *rb = getReadRingBuffer(ch); + + if (rb) { + + // this is marginally more likely to leave our channels in + // sync after a processing failure than just passing "count": + 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 = " <<reqd << ", channels = " << channels << ", ic = " << m_stretcherInputCount << endl; +#endif + + for (int c = 0; c < channels; ++c) { + if (c >= m_stretcherInputCount) continue; + if (reqd > m_stretcherInputSizes[c]) { + if (c == 0) { + cerr << "WARNING: resizing stretcher input buffer from " << m_stretcherInputSizes[c] << " to " << (reqd * 2) << endl; + } + delete[] m_stretcherInputs[c]; + m_stretcherInputSizes[c] = reqd * 2; + m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; + } + } + + for (int c = 0; c < channels; ++c) { + if (c >= m_stretcherInputCount) continue; + RingBuffer<float> *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<float> *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<float> *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<float> *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<Model *>::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<float> *wb = getWriteRingBuffer(c); + if (wb) { + if (wb->getReadSpace() < m_blockSize * 2) { + if ((m_writeBufferFill + m_blockSize * 2) < + m_lastModelEndFrame) { + // OK, we don't have enough and there's more to + // read -- don't unify until we can do better +#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<float> *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<float> *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<float> *rb = s.getReadRingBuffer(c); + if (rb) rb->reset(); + } + } + previouslyPlaying = playing; + + work = s.fillBuffers(); + } + + s.m_mutex.unlock(); +} +
--- /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 <bqaudioio/ApplicationPlaybackSource.h> + +#include <QObject> +#include <QMutex> +#include <QWaitCondition> + +#include "base/Thread.h" +#include "base/RealTime.h" + +#include <samplerate.h> + +#include <set> +#include <map> + +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::set<Model *>s); + + /** + * 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<RingBuffer<float> *> { + public: + virtual ~RingBufferVector() { + while (!empty()) { + delete *begin(); + erase(begin()); + } + } + }; + + std::set<Model *> m_models; + RingBufferVector *m_readBuffers; + RingBufferVector *m_writeBuffers; + sv_frame_t m_readBufferFill; + sv_frame_t m_writeBufferFill; + Scavenger<RingBufferVector> 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<RealTimePluginInstance> m_pluginScavenger; + sv_frame_t m_playStartFrame; + bool m_playStartFramePassed; + RealTime m_playStartedAt; + + RingBuffer<float> *getWriteRingBuffer(int c) { + if (m_writeBuffers && c < (int)m_writeBuffers->size()) { + return (*m_writeBuffers)[c]; + } else { + return 0; + } + } + + RingBuffer<float> *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<RealTime> m_rangeStarts; + std::vector<RealTime> 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 + +
--- /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 <iostream> +#include <cmath> + +#include <QDir> +#include <QFile> + +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<DenseTimeValueModel *>(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<const Model *>(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<const SparseOneDimensionalModel *>(model) || + qobject_cast<const NoteModel *>(model) || + qobject_cast<const FlexiNoteModel *>(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<const NoteModel *>(model) || + qobject_cast<const FlexiNoteModel *>(model)); + return does; +} + +bool +AudioGenerator::usesContinuousSynth(const Model *model) +{ + bool cont = + (qobject_cast<const SparseTimeValueModel *>(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<SparseOneDimensionalModel *>(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<Model *> 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<DenseTimeValueModel *>(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<NoteExportable *>(model); + if (exportable) { + notes = exportable->getNotesWithin(reqStart, + reqStart + m_processingBlockSize); + } + + std::vector<ClipMixer::NoteStart> starts; + std::vector<ClipMixer::NoteEnd> 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<SparseTimeValueModel *>(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; +} +
--- /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 <QObject> +#include <QMutex> + +#include <set> +#include <map> +#include <vector> + +#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::set<Model *>s); + + /** + * 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<Model *> 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<const Model *, ClipMixer *> ClipMixerMap; + + typedef std::multiset<NoteOff, NoteOff::Comparator> NoteOffSet; + typedef std::map<const Model *, NoteOffSet> NoteOffMap; + + typedef std::map<const Model *, ContinuousSynth *> 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 +
--- /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 <QDir> + +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(); +} + +
--- /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 <bqaudioio/ApplicationRecordTarget.h> + +#include <string> + +#include <QObject> +#include <QMutex> + +#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
--- /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 <sndfile.h> +#include <cmath> + +#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<NoteStart> newNotes, + std::vector<NoteEnd> endingNotes) +{ + foreach (NoteStart note, newNotes) { + if (note.frequency > 20 && + note.frequency < 5000) { + m_playing.push_back(note); + } + } + + std::vector<NoteStart> 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); + } + } +} + +
--- /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 <QString> +#include <vector> + +#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<NoteStart> newNotes, + std::vector<NoteEnd> 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<NoteStart> 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
--- /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 <cmath> + +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; +} +
--- /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
--- /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 <iostream> +#include <cmath> + +// 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 "%"; +}
--- /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
--- 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 <rubberband/RubberBandStretcher.h> -using namespace RubberBand; - -#include <iostream> -#include <cassert> - -//#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<DenseTimeValueModel *>(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<Model *>::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<WaveFileModel *>(*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<Model *>::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<float>(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<float> *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<float> *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<RealTimePluginInstance *>(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<Model *> 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<float> *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<float> *rb = getReadRingBuffer(ch); - - if (rb) { - - // this is marginally more likely to leave our channels in - // sync after a processing failure than just passing "count": - size_t request = count; - if (ch > 0) request = got; - - got = rb->read(buffer[ch], request); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - 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 = " <<reqd << ", channels = " << channels << ", ic = " << m_stretcherInputCount << endl; -#endif - - for (size_t c = 0; c < channels; ++c) { - if (c >= m_stretcherInputCount) continue; - if (reqd > m_stretcherInputSizes[c]) { - if (c == 0) { - cerr << "WARNING: resizing stretcher input buffer from " << m_stretcherInputSizes[c] << " to " << (reqd * 2) << endl; - } - delete[] m_stretcherInputs[c]; - m_stretcherInputSizes[c] = reqd * 2; - m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; - } - } - - for (size_t c = 0; c < channels; ++c) { - if (c >= m_stretcherInputCount) continue; - RingBuffer<float> *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<float> *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<float> *wb = getWriteRingBuffer(c); - if (wb) wb->write(tmp, toCopy); - } - - m_writeBufferFill = f; - if (readWriteEqual) m_readBufferFill = f; - - } else { - - // space must be a multiple of generatorBlockSize - 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<float> *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<Model *>::iterator mi = m_models.begin(); - mi != m_models.end(); ++mi) { - - got = m_audioGenerator->mixModel(*mi, chunkStart, - chunkSize, chunkBufferPtrs, - fadeIn, fadeOut); - } - - for (size_t c = 0; c < channels; ++c) { - chunkBufferPtrs[c] += chunkSize; - } - - processed += chunkSize; - chunkStart = nextChunkStart; - } - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - 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<float> *wb = getWriteRingBuffer(c); - if (wb) { - if (wb->getReadSpace() < m_blockSize * 2) { - if ((m_writeBufferFill + m_blockSize * 2) < - m_lastModelEndFrame) { - // OK, we don't have enough and there's more to - // read -- don't unify until we can do better -#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<float> *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<float> *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<float> *rb = s.getReadRingBuffer(c); - if (rb) rb->reset(); - } - } - previouslyPlaying = playing; - - work = s.fillBuffers(); - } - - s.m_mutex.unlock(); -} -
--- 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 <QObject> -#include <QMutex> -#include <QWaitCondition> - -#include "base/Thread.h" -#include "base/RealTime.h" - -#include <samplerate.h> - -#include <set> -#include <map> - -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::set<Model *>s); - - /** - * 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<RingBuffer<float> *> { - public: - virtual ~RingBufferVector() { - while (!empty()) { - delete *begin(); - erase(begin()); - } - } - }; - - std::set<Model *> m_models; - RingBufferVector *m_readBuffers; - RingBufferVector *m_writeBuffers; - size_t m_readBufferFill; - size_t m_writeBufferFill; - Scavenger<RingBufferVector> m_bufferScavenger; - size_t m_sourceChannelCount; - size_t m_blockSize; - size_t m_sourceSampleRate; - size_t m_targetSampleRate; - size_t m_playLatency; - 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<RealTimePluginInstance> m_pluginScavenger; - size_t m_playStartFrame; - bool m_playStartFramePassed; - RealTime m_playStartedAt; - - RingBuffer<float> *getWriteRingBuffer(size_t c) { - if (m_writeBuffers && c < m_writeBuffers->size()) { - return (*m_writeBuffers)[c]; - } else { - return 0; - } - } - - RingBuffer<float> *getReadRingBuffer(size_t c) { - RingBufferVector *rb = m_readBuffers; - if (rb && c < rb->size()) { - return (*rb)[c]; - } else { - return 0; - } - } - - void clearRingBuffers(bool haveLock = false, size_t count = 0); - void unifyRingBuffers(); - - 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<RealTime> m_rangeStarts; - std::vector<RealTime> 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 - -
--- 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 <iostream> - -AudioCallbackPlayTarget::AudioCallbackPlayTarget(AudioCallbackPlaySource *source) : - m_source(source), - m_outputGain(1.0) -{ - if (m_source) { - connect(m_source, SIGNAL(modelReplaced()), - this, SLOT(sourceModelReplaced())); - } -} - -AudioCallbackPlayTarget::~AudioCallbackPlayTarget() -{ -} - -void -AudioCallbackPlayTarget::setOutputGain(float gain) -{ - m_outputGain = gain; -} -
--- 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 <QObject> - -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 -
--- 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
--- 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 <jack/jack.h> -#include <vector> - -#include <CoreAudio/CoreAudio.h> -#include <CoreAudio/CoreAudioTypes.h> -#include <AudioUnit/AUComponent.h> -#include <AudioUnit/AudioUnitProperties.h> -#include <AudioUnit/AudioUnitParameters.h> -#include <AudioUnit/AudioOutputUnit.h> - -#include "AudioCallbackPlayTarget.h" - -class AudioCallbackPlaySource; - -class AudioCoreAudioTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioCoreAudioTarget(AudioCallbackPlaySource *source); - ~AudioCoreAudioTarget(); - - virtual bool isOK() const; - -public slots: - virtual void sourceModelReplaced(); - -protected: - OSStatus process(void *data, - AudioUnitRenderActionFlags *flags, - const AudioTimeStamp *timestamp, - unsigned int inbus, - unsigned int inframes, - AudioBufferList *ioData); - - int m_bufferSize; - int m_sampleRate; - int m_latency; -}; - -#endif /* HAVE_COREAUDIO */ - -#endif -
--- a/audioio/AudioGenerator.cpp 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 <iostream> -#include <cmath> - -#include <QDir> -#include <QFile> - -const size_t -AudioGenerator::m_pluginBlockSize = 2048; - -QString -AudioGenerator::m_sampleDir = ""; - -//#define DEBUG_AUDIO_GENERATOR 1 - -AudioGenerator::AudioGenerator() : - m_sourceSampleRate(0), - m_targetChannelCount(1), - 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<DenseTimeValueModel *>(model); - - if (dtvm) { - m_sourceSampleRate = model->getSampleRate(); - return true; - } - } - - RealTimePluginInstance *plugin = loadPluginFor(model); - if (plugin) { - QMutexLocker locker(&m_mutex); - m_synthMap[model] = plugin; - return true; - } - - return false; -} - -void -AudioGenerator::playPluginIdChanged(const Playable *playable, QString) -{ - const Model *model = dynamic_cast<const Model *>(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<const Model *>(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<SparseOneDimensionalModel *>(model); - if (!sodm) return; // nothing to do - - QMutexLocker locker(&m_mutex); - - if (m_synthMap.find(sodm) == m_synthMap.end()) return; - - RealTimePluginInstance *instance = m_synthMap[sodm]; - m_synthMap.erase(sodm); - delete instance; -} - -void -AudioGenerator::clearModels() -{ - QMutexLocker locker(&m_mutex); - while (!m_synthMap.empty()) { - RealTimePluginInstance *instance = m_synthMap.begin()->second; - m_synthMap.erase(m_synthMap.begin()); - delete instance; - } -} - -void -AudioGenerator::reset() -{ - QMutexLocker locker(&m_mutex); - for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { - if (i->second) { - i->second->silence(); - i->second->discardEvents(); - } - } - - m_noteOffs.clear(); -} - -void -AudioGenerator::setTargetChannelCount(size_t targetChannelCount) -{ - if (m_targetChannelCount == targetChannelCount) return; - -// 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<Model *> 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<DenseTimeValueModel *>(model); - if (dtvm) { - return mixDenseTimeValueModel(dtvm, startFrame, frameCount, - buffer, gain, pan, fadeIn, fadeOut); - } - - bool synthetic = - (qobject_cast<SparseOneDimensionalModel *>(model) || - qobject_cast<NoteModel *>(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<SparseOneDimensionalModel *>(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<NoteModel *>(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; -} -
--- 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 <QObject> -#include <QMutex> - -#include <set> -#include <map> -#include <vector> - -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::set<Model *>s); - - /** - * 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<Model *> 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<NoteData> 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<const Model *, RealTimePluginInstance *> PluginMap; - - typedef std::multiset<NoteOff, NoteOff::Comparator> NoteOffSet; - typedef std::map<const Model *, NoteOffSet> NoteOffMap; - - QMutex m_mutex; - PluginMap m_synthMap; - NoteOffMap m_noteOffs; - static QString m_sampleDir; - - virtual RealTimePluginInstance *loadPluginFor(const Model *model); - virtual RealTimePluginInstance *loadPlugin(QString id, QString program); - static 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 -
--- 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 <iostream> -#include <cmath> - -#include <alloca.h> - -//#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<const char *, void *> 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<jack_port_t *>::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<jack_port_t *>::iterator itr = m_outputs.end(); - --itr; - jack_port_t *port = *itr; - if (port) jack_port_unregister(m_client, port); - m_outputs.erase(itr); - } - - m_mutex.unlock(); -} - -int -AudioJACKTarget::process(jack_nframes_t nframes) -{ - if (m_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 */ -
--- 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 <jack/jack.h> -#include <vector> - -#include "AudioCallbackPlayTarget.h" - -#include <QMutex> - -class AudioCallbackPlaySource; - -class AudioJACKTarget : public AudioCallbackPlayTarget -{ - Q_OBJECT - -public: - AudioJACKTarget(AudioCallbackPlaySource *source); - virtual ~AudioJACKTarget(); - - virtual 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<jack_port_t *> m_outputs; - jack_nframes_t m_bufferSize; - jack_nframes_t m_sampleRate; - QMutex m_mutex; - bool m_done; -}; - -#endif /* HAVE_JACK */ - -#endif -
--- 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 <iostream> -#include <cassert> -#include <cmath> - -#ifndef _WIN32 -#include <pthread.h> -#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 */ -
--- 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 <portaudio.h> - -#include <QObject> - -#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 -
--- 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 <QMutexLocker> - -#include <iostream> -#include <cassert> -#include <cmath> - -#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 */ -
--- 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 <pulse/pulseaudio.h> - -#include <QObject> -#include <QMutex> -#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 -
--- 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 <QCoreApplication> - -#include <iostream> - -AudioTargetFactory * -AudioTargetFactory::m_instance = 0; - -AudioTargetFactory * -AudioTargetFactory::getInstance() -{ - if (!m_instance) m_instance = new AudioTargetFactory(); - return m_instance; -} - -AudioTargetFactory::AudioTargetFactory() -{ -} - -std::vector<QString> -AudioTargetFactory::getCallbackTargetNames(bool includeAuto) const -{ - std::vector<QString> 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; -} - -
--- 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 <vector> -#include <QString> - -#include "base/Debug.h" - -class AudioCallbackPlaySource; -class AudioCallbackPlayTarget; - -class AudioTargetFactory -{ -public: - static AudioTargetFactory *getInstance(); - - std::vector<QString> 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 -
--- 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 <iostream> -#include <cmath> - -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 "%"; -}
--- 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
--- 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
--- 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 <typename T> + 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<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(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 <typename T> + 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<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(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
--- 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])
--- 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 <QApplication> #include <QTextStream> #include <QSettings> @@ -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<Layer *> layers = createDerivedLayers(transforms, input); + if (layers.empty()) return 0; + else return layers[0]; +} + +vector<Layer *> +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<Model *> newModels = addDerivedModels(transforms, input, message, 0); + + if (newModels.empty()) { + //!!! This identifier may be wrong! + emit modelGenerationFailed(transforms[0].getIdentifier(), message); + return vector<Layer *>(); } 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<Layer *> 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<Layer *> layers) { + m_primary = layers; + } + + void + moreModelsAvailable(vector<Model *> 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<Layer *> 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<Layer *>()); + delete this; + } + + void cancel() { + foreach (Layer *layer, m_primary) { + Model *model = layer->getModel(); + if (model) { + model->abandon(); + } + } + } + +private: + Document *m_doc; + vector<Layer *> 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<Model *> 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<Layer *> 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<AdditionalModelConverter *>(h); + conv->cancel(); +} + +vector<Layer *> +Document::createLayersForDerivedModels(vector<Model *> newModels, + QStringList names) +{ + vector<Layer *> 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<Layer *>(); + } + + //!!! 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<RangeSummarisableTimeValueModel *>(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<Model *> 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<Model *> +Document::addDerivedModels(const Transforms &transforms, + const ModelTransformer::Input &input, + QString &message, + AdditionalModelConverter *amc) +{ + vector<Model *> 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<RangeSummarisableTimeValueModel *>(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;
--- 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 <map> @@ -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<Layer *> 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<Layer *> primary, + std::vector<Layer *> 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<Model *> 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<Model *, ModelRecord> 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<Layer *> createLayersForDerivedModels(std::vector<Model *>, + QStringList names); + /** * And these are the layers. We also control the lifespans of * these (usually through the commands used to add and remove them).
--- 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 <bqaudioio/SystemPlaybackTarget.h> +#include <bqaudioio/SystemAudioIO.h> +#include <bqaudioio/AudioFactory.h> + #include <QApplication> #include <QMessageBox> #include <QGridLayout> @@ -87,6 +91,7 @@ #include <QFileInfo> #include <QDir> #include <QTextStream> +#include <QTextCodec> #include <QProcess> #include <QShortcut> #include <QSettings> @@ -96,14 +101,12 @@ #include <QRegExp> #include <QScrollArea> #include <QDesktopWidget> +#include <QSignalMapper> #include <iostream> #include <cstdio> #include <errno.h> - - - 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>("sv_frame_t"); + qRegisterMetaType<sv_samplerate_t>("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<QMenu *> menus = mb->findChildren<QMenu *> + (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<QAbstractButton *>(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<QAction *>(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<NoteLayer *>(currentLayer) || + dynamic_cast<FlexiNoteLayer *>(currentLayer) || dynamic_cast<RegionLayer *>(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<SparseOneDimensionalModel::Point> (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<NoteModel *>(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<FlexiNoteModel *>(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("<b>No audio available</b><p>Could not open an audio device for playback.<p>Automatic audio device detection failed. Audio playback will not be available during this session.</p>"), 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<QFrame *> frames = statusBar()->findChildren<QFrame *>(); + 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<QAction *>(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<QAction *>(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 + } } -
--- 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<QShortcut *> m_appShortcuts; + virtual bool shouldCreateNewSessionForRDFAudio(bool *) { return true; } virtual void connectLayerEditDialog(ModelDataTableDialog *dialog);
--- 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<NoteModel *>(model)) good = true; + else if (dynamic_cast<FlexiNoteModel *>(model)) good = true; else if (dynamic_cast<RegionModel *>(model)) good = true; else if (dynamic_cast<EditableDenseThreeDimensionalModel *>(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<FlexiNoteModel *>(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<RegionModel *>(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 = "<plugin"; - + for (int i = 0; i < attributes.length(); ++i) { configurationXml += QString(" %1=\"%2\"") .arg(attributes.qName(i)) @@ -1310,12 +1370,21 @@ configurationXml += "/>"; - if (m_currentPlayParameters) { - m_currentPlayParameters->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;
--- 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 &);
--- 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 <typeinfo> +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<Vamp::Plugin *>(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<QString, Model *> &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,
--- 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<QString, Model *> &modelMap, QStringList candidateModelNames, QString defaultModelName); + static void setParentWidget(QWidget *); + private: bool getChannelRange(TransformId identifier, Vamp::PluginBase *plugin, int &min, int &max);
--- 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); } }
--- 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 -
--- 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 \