view audioio/AudioCallbackPlaySource.h @ 4:5865094175ea

* Fix update and play limits for play-selection mode when not looping * Fix playback in loop mode when no selection -- but the GUI update for this is still wrong on the flyback * Various fixes and improvements to making selections, particularly during playback * Draw selection under non-opaque non-scrollable layers, so as to improve cacheing * Show selection limits as text when drawing selection * Allow user to find missing audio files when loading session * Cross-fade selections when in play-selection mode -- mostly. We don't cross-fade on a processing block boundary, and unfortunately with short selections the selection boundary is quite likely to coincide with a block boundary.
author Chris Cannam
date Wed, 25 Jan 2006 17:46:28 +0000
parents 75c3ea1c3a32
children f3d777b693f7
line wrap: on
line source
/* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */

/*
    A waveform viewer and audio annotation editor.
    Chris Cannam, Queen Mary University of London, 2005-2006
    
    This is experimental software.  Not for distribution.
*/

#ifndef _AUDIO_CALLBACK_PLAY_SOURCE_H_
#define _AUDIO_CALLBACK_PLAY_SOURCE_H_

#include "base/RingBuffer.h"
#include "base/AudioPlaySource.h"
#include "base/Scavenger.h"

#include <QObject>
#include <QMutex>
#include <QWaitCondition>
#include <QThread>

#include <samplerate.h>

#include <set>
#include <map>

class Model;
class ViewManager;
class AudioGenerator;
class IntegerTimeStretcher;

/**
 * AudioCallbackPlaySource manages audio data supply to callback-based
 * audio APIs such as JACK or CoreAudio.  It maintains one ring buffer
 * per channel, filled during playback by a non-realtime thread, and
 * provides a method for a realtime thread to pick up the latest
 * available sample data from these buffers.
 */
class AudioCallbackPlaySource : public virtual QObject,
				public AudioPlaySource
{
    Q_OBJECT

public:
    AudioCallbackPlaySource(ViewManager *);
    virtual ~AudioCallbackPlaySource();
    
    /**
     * Add a data model to be played from.  The source can mix
     * playback from a number of sources including dense and sparse
     * models.  The models must match in sample rate, but they don't
     * have to have identical numbers of channels.
     */
    virtual void addModel(Model *model);

    /**
     * Remove a model.
     */
    virtual void removeModel(Model *model);

    /**
     * Remove all models.  (Silence will ensue.)
     */
    virtual void clearModels();

    /**
     * Start making data available in the ring buffers for playback,
     * from the given frame.  If playback is already under way, reseek
     * to the given frame and continue.
     */
    virtual void play(size_t startFrame);

    /**
     * Stop playback and ensure that no more data is returned.
     */
    virtual void stop();

    /**
     * Return whether playback is currently supposed to be happening.
     */
    virtual bool isPlaying() const { return m_playing; }

    /**
     * Return the frame number that is currently expected to be coming
     * out of the speakers.  (i.e. compensating for playback latency.)
     */
    virtual size_t getCurrentPlayingFrame();

    /**
     * Set the block size of the target audio device.  This should
     * be called by the target class.
     */
    void setTargetBlockSize(size_t);

    /**
     * Get the block size of the target audio device.
     */
    size_t getTargetBlockSize() const;

    /**
     * Set the playback latency of the target audio device, in frames
     * at the target sample rate.  This is the difference between the
     * frame currently "leaving the speakers" and the last frame (or
     * highest last frame across all channels) requested via
     * getSamples().  The default is zero.
     */
    void setTargetPlayLatency(size_t);

    /**
     * Get the playback latency of the target audio device.
     */
    size_t getTargetPlayLatency() const;

    /**
     * Specify that the target audio device has a fixed sample rate
     * (i.e. cannot accommodate arbitrary sample rates based on the
     * source).  If the target sets this to something other than the
     * source sample rate, this class will resample automatically to
     * fit.
     */
    void setTargetSampleRate(size_t);

    /**
     * Return the sample rate set by the target audio device (or the
     * source sample rate if the target hasn't set one).
     */
    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 will be available.
     * This may safely be called from a realtime thread.  Returns 0 if
     * there is no source yet available.
     */
    size_t getSourceChannelCount() const;

    /**
     * Get the actual sample rate of the source material.  This may
     * safely be called from a realtime thread.  Returns 0 if there is
     * no source yet available.
     */
    size_t getSourceSampleRate() const;

    /**
     * Get "count" samples (at the target sample rate) of the mixed
     * audio data, in all channels.  This may safely be called from a
     * realtime thread.
     */
    size_t getSourceSamples(size_t count, float **buffer);

    void setSlowdownFactor(size_t factor);

signals:
    void modelReplaced();

    void playStatusChanged(bool isPlaying);

    /// Just a warning
    void sampleRateMismatch(size_t requested, size_t available);

protected slots:
    void selectionChanged();
    void playLoopModeChanged();
    void playSelectionModeChanged();

protected:
    ViewManager                     *m_viewManager;
    AudioGenerator                  *m_audioGenerator;

    std::set<Model *>                m_models;
    std::vector<RingBuffer<float> *> m_buffers;
    size_t                           m_bufferCount;
    size_t                           m_blockSize;
    size_t                           m_sourceSampleRate;
    size_t                           m_targetSampleRate;
    size_t                           m_playLatency;
    bool                             m_playing;
    bool                             m_exiting;
    size_t                           m_bufferedToFrame;
    size_t                           m_lastModelEndFrame;
    static const size_t              m_ringBufferSize;
    float                            m_outputLeft;
    float                            m_outputRight;

    RingBuffer<float> &getRingBuffer(size_t c) {
	return *m_buffers[c];
    }

    class TimeStretcherData
    {
    public:
	TimeStretcherData(size_t channels, size_t factor, size_t blockSize);
	~TimeStretcherData();

	size_t getFactor() const { return m_factor; }
	IntegerTimeStretcher *getStretcher(size_t channel);
	double *getOutputBuffer(size_t channel);
	double *getInputBuffer();
	
	void run(size_t channel);

    protected:
	TimeStretcherData(const TimeStretcherData &); // not provided
	TimeStretcherData &operator=(const TimeStretcherData &); // not provided

	typedef std::pair<IntegerTimeStretcher *, double *> StretcherBuffer;
	std::map<size_t, StretcherBuffer> m_stretcher;
	double *m_stretchInputBuffer;
	size_t m_factor;
	size_t m_blockSize;
    };

    size_t m_slowdownCounter;
    TimeStretcherData *m_timeStretcher;
    Scavenger<TimeStretcherData> m_timeStretcherScavenger;

    // Called from fill thread, m_playing true, mutex held
    void fillBuffers();
    
    // Called from fillBuffers
    bool mixModels(size_t &frame, size_t count, float **buffers);

    class AudioCallbackPlaySourceFillThread : public QThread
    {
    public:
	AudioCallbackPlaySourceFillThread(AudioCallbackPlaySource &source) :
	    m_source(source) { }

	virtual void run();

    protected:
	AudioCallbackPlaySource &m_source;
    };

    QMutex m_mutex;
    QWaitCondition m_condition;
    AudioCallbackPlaySourceFillThread *m_fillThread;
    SRC_STATE *m_converter;
};

#endif