changeset 13:89bb89894ad6

* As previous commit
author Chris Cannam
date Fri, 17 Feb 2006 18:11:08 +0000
parents 29b38a641d43
children 0f7997490bae
files audioio/AudioCallbackPlaySource.cpp audioio/AudioCallbackPlaySource.h audioio/AudioGenerator.cpp audioio/AudioGenerator.h audioio/AudioJACKTarget.cpp
diffstat 5 files changed, 225 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/audioio/AudioCallbackPlaySource.cpp	Fri Feb 17 18:04:26 2006 +0000
+++ b/audioio/AudioCallbackPlaySource.cpp	Fri Feb 17 18:11:08 2006 +0000
@@ -22,6 +22,7 @@
 #include <cassert>
 
 //#define DEBUG_AUDIO_PLAY_SOURCE 1
+//#define DEBUG_AUDIO_PLAY_SOURCE_PLAYING 1
 
 //const size_t AudioCallbackPlaySource::m_ringBufferSize = 102400;
 const size_t AudioCallbackPlaySource::m_ringBufferSize = 131071;
@@ -31,6 +32,9 @@
     m_audioGenerator(new AudioGenerator(manager)),
     m_readBuffers(0),
     m_writeBuffers(0),
+    m_readBufferFill(0),
+    m_writeBufferFill(0),
+    m_bufferScavenger(1),
     m_sourceChannelCount(0),
     m_blockSize(1024),
     m_sourceSampleRate(0),
@@ -38,7 +42,6 @@
     m_playLatency(0),
     m_playing(false),
     m_exiting(false),
-    m_bufferedToFrame(0),
     m_lastModelEndFrame(0),
     m_outputLeft(0.0),
     m_outputRight(0.0),
@@ -119,12 +122,8 @@
 
     std::cerr << "Adding model with " << modelChannels << " channels " << std::endl;
 
-    if (!m_writeBuffers || m_writeBuffers->size() < modelChannels) {
-	m_audioGenerator->setTargetChannelCount(modelChannels);
-    }
-
-    if (!m_writeBuffers || (m_writeBuffers->size() < modelChannels)) {
-	clearRingBuffers(true, modelChannels);
+    if (!m_writeBuffers || (m_writeBuffers->size() < getTargetChannelCount())) {
+	clearRingBuffers(true, getTargetChannelCount());
 	buffersChanged = true;
     } else {
 	if (canPlay) clearRingBuffers(true);
@@ -139,6 +138,8 @@
 
     m_mutex.unlock();
 
+    m_audioGenerator->setTargetChannelCount(getTargetChannelCount());
+
     if (!m_fillThread) {
 	m_fillThread = new AudioCallbackPlaySourceFillThread(*this);
 	m_fillThread->start();
@@ -214,6 +215,19 @@
 	if (m_writeBuffers) count = m_writeBuffers->size();
     }
 
+    size_t sf = m_readBufferFill;
+    RingBuffer<float> *rb = getReadRingBuffer(0);
+    if (rb) {
+	//!!! This is incorrect if we're in a non-contiguous selection
+	//Same goes for all related code (subtracting the read space
+	//from the fill frame to try to establish where the effective
+	//pre-resample/timestretch read pointer is)
+	size_t rs = rb->getReadSpace();
+	if (rs < sf) sf -= rs;
+	else sf = 0;
+    }
+    m_writeBufferFill = sf;
+
     if (m_readBuffers != m_writeBuffers) {
 	delete m_writeBuffers;
     }
@@ -263,9 +277,9 @@
 
     m_mutex.lock();
     if (m_playing) {
-	m_bufferedToFrame = startFrame;
+	m_readBufferFill = m_writeBufferFill = startFrame;
 	if (m_readBuffers) {
-	    for (size_t c = 0; c < getSourceChannelCount(); ++c) {
+	    for (size_t c = 0; c < getTargetChannelCount(); ++c) {
 		RingBuffer<float> *rb = getReadRingBuffer(c);
 		if (rb) rb->reset();
 	    }
@@ -273,7 +287,7 @@
 	if (m_converter) src_reset(m_converter);
     } else {
 	if (m_converter) src_reset(m_converter);
-	m_bufferedToFrame = startFrame;
+	m_readBufferFill = m_writeBufferFill = startFrame;
     }
     m_mutex.unlock();
 
@@ -359,7 +373,7 @@
     }
 
     size_t readSpace = 0;
-    for (size_t c = 0; c < getSourceChannelCount(); ++c) {
+    for (size_t c = 0; c < getTargetChannelCount(); ++c) {
 	RingBuffer<float> *rb = getReadRingBuffer(c);
 	if (rb) {
 	    size_t spaceHere = rb->getReadSpace();
@@ -380,7 +394,7 @@
     }
 
     latency += readSpace;
-    size_t bufferedFrame = m_bufferedToFrame;
+    size_t bufferedFrame = m_readBufferFill;
 
     bool looping = m_viewManager->getPlayLoopMode();
     bool constrained = (m_viewManager->getPlaySelectionMode() &&
@@ -489,7 +503,8 @@
     if (getSourceSampleRate() != getTargetSampleRate()) {
 
 	int err = 0;
-	m_converter = src_new(SRC_SINC_BEST_QUALITY, m_sourceChannelCount, &err);
+	m_converter = src_new(SRC_SINC_BEST_QUALITY,
+			      getTargetChannelCount(), &err);
 	if (!m_converter) {
 	    std::cerr
 		<< "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: "
@@ -514,6 +529,13 @@
 }
 
 size_t
+AudioCallbackPlaySource::getTargetChannelCount() const
+{
+    if (m_sourceChannelCount < 2) return 2;
+    return m_sourceChannelCount;
+}
+
+size_t
 AudioCallbackPlaySource::getSourceSampleRate() const
 {
     return m_sourceSampleRate;
@@ -590,7 +612,7 @@
 
     if (factor > 1) {
 	TimeStretcherData *newStretcher = new TimeStretcherData
-	    (getSourceChannelCount(), factor, getTargetBlockSize());
+	    (getTargetChannelCount(), factor, getTargetBlockSize());
 	m_slowdownCounter = 0;
 	m_timeStretcher = newStretcher;
     } else {
@@ -606,7 +628,7 @@
 AudioCallbackPlaySource::getSourceSamples(size_t count, float **buffer)
 {
     if (!m_playing) {
-	for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
+	for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
 	    for (size_t i = 0; i < count; ++i) {
 		buffer[ch][i] = 0.0;
 	    }
@@ -620,7 +642,7 @@
 
 	size_t got = 0;
 
-	for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
+	for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
 
 	    RingBuffer<float> *rb = getReadRingBuffer(ch);
 
@@ -638,7 +660,7 @@
 #endif
 	    }
 
-	    for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
+	    for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
 		for (size_t i = got; i < count; ++i) {
 		    buffer[ch][i] = 0.0;
 		}
@@ -654,7 +676,7 @@
 	size_t got = 0;
 	double *ib = timeStretcher->getInputBuffer();
 
-	for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
+	for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
 
 	    RingBuffer<float> *rb = getReadRingBuffer(ch);
 
@@ -682,7 +704,7 @@
 	m_slowdownCounter = 0;
     }
 
-    for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
+    for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
 
 	double *ob = timeStretcher->getOutputBuffer(ch);
 
@@ -708,7 +730,7 @@
     static size_t tmpSize = 0;
 
     size_t space = 0;
-    for (size_t c = 0; c < getSourceChannelCount(); ++c) {
+    for (size_t c = 0; c < getTargetChannelCount(); ++c) {
 	RingBuffer<float> *wb = getWriteRingBuffer(c);
 	if (wb) {
 	    size_t spaceHere = wb->getWriteSpace();
@@ -718,12 +740,14 @@
     
     if (space == 0) return false;
 
+    size_t f = m_writeBufferFill;
+	
+    bool readWriteEqual = (m_readBuffers == m_writeBuffers);
+
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
     std::cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << std::endl;
 #endif
 
-    size_t f = m_bufferedToFrame;
-	
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
     std::cout << "buffered to " << f << " already" << std::endl;
 #endif
@@ -734,7 +758,8 @@
     std::cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << std::endl;
 #endif
 
-    size_t channels = getSourceChannelCount();
+    size_t channels = getTargetChannelCount();
+
     size_t orig = space;
     size_t got = 0;
 
@@ -843,8 +868,9 @@
 	    if (wb) wb->write(tmp, toCopy);
 	}
 
-	m_bufferedToFrame = f;
-	
+	m_writeBufferFill = f;
+	if (readWriteEqual) m_readBufferFill = f;
+
     } else {
 
 	// space must be a multiple of generatorBlockSize
@@ -881,7 +907,9 @@
 #endif
 	}
 
-	m_bufferedToFrame = f;
+	m_writeBufferFill = f;
+	if (readWriteEqual) m_readBufferFill = f;
+
 	//!!! how do we know when ended? need to mark up a fully-buffered flag and check this if we find the buffers empty in getSourceSamples
     }
 
@@ -903,7 +931,7 @@
 
     static float **chunkBufferPtrs = 0;
     static size_t chunkBufferPtrCount = 0;
-    size_t channels = getSourceChannelCount();
+    size_t channels = getTargetChannelCount();
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
     std::cerr << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << std::endl;
@@ -1041,6 +1069,68 @@
 }
 
 void
+AudioCallbackPlaySource::unifyRingBuffers()
+{
+    if (m_readBuffers == m_writeBuffers) return;
+
+    // only unify if there will be something to read
+    for (size_t c = 0; c < getTargetChannelCount(); ++c) {
+	RingBuffer<float> *wb = getWriteRingBuffer(c);
+	if (wb) {
+	    if (wb->getReadSpace() < m_blockSize * 2) {
+		if ((m_writeBufferFill + m_blockSize * 2) < 
+		    m_lastModelEndFrame) {
+		    // OK, we don't have enough and there's more to
+		    // read -- don't unify until we can do better
+		    return;
+		}
+	    }
+	    break;
+	}
+    }
+
+    size_t rf = m_readBufferFill;
+    RingBuffer<float> *rb = getReadRingBuffer(0);
+    if (rb) {
+	size_t rs = rb->getReadSpace();
+	//!!! incorrect when in non-contiguous selection, see comments elsewhere
+//	std::cerr << "rs = " << rs << std::endl;
+	if (rs < rf) rf -= rs;
+	else rf = 0;
+    }
+    
+    //std::cerr << "m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << std::endl;
+
+    size_t wf = m_writeBufferFill;
+    size_t skip = 0;
+    for (size_t c = 0; c < getTargetChannelCount(); ++c) {
+	RingBuffer<float> *wb = getWriteRingBuffer(c);
+	if (wb) {
+	    if (c == 0) {
+		
+		size_t wrs = wb->getReadSpace();
+//		std::cerr << "wrs = " << wrs << std::endl;
+
+		if (wrs < wf) wf -= wrs;
+		else wf = 0;
+//		std::cerr << "wf = " << wf << std::endl;
+		
+		if (wf < rf) skip = rf - wf;
+		if (skip == 0) break;
+	    }
+
+//	    std::cerr << "skipping " << skip << std::endl;
+	    wb->skip(skip);
+	}
+    }
+		    
+    m_bufferScavenger.claim(m_readBuffers);
+    m_readBuffers = m_writeBuffers;
+    m_readBufferFill = m_writeBufferFill;
+    std::cerr << "unified" << std::endl;
+}
+
+void
 AudioCallbackPlaySource::AudioCallbackPlaySourceFillThread::run()
 {
     AudioCallbackPlaySource &s(m_source);
@@ -1056,12 +1146,7 @@
 
     while (!s.m_exiting) {
 
-	if (s.m_readBuffers != s.m_writeBuffers) {
-	    s.m_bufferScavenger.claim(s.m_readBuffers);
-	    s.m_readBuffers = s.m_writeBuffers;
-	    std::cerr << "unified" << std::endl;
-	}
-	
+	s.unifyRingBuffers();
 	s.m_bufferScavenger.scavenge();
 	s.m_timeStretcherScavenger.scavenge();
 
@@ -1100,7 +1185,7 @@
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
 	    std::cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << std::endl;
 #endif
-	    for (size_t c = 0; c < s.getSourceChannelCount(); ++c) {
+	    for (size_t c = 0; c < s.getTargetChannelCount(); ++c) {
 		RingBuffer<float> *rb = s.getReadRingBuffer(c);
 		if (rb) rb->reset();
 	    }
--- a/audioio/AudioCallbackPlaySource.h	Fri Feb 17 18:04:26 2006 +0000
+++ b/audioio/AudioCallbackPlaySource.h	Fri Feb 17 18:11:08 2006 +0000
@@ -140,13 +140,23 @@
     virtual bool getOutputLevels(float &left, float &right);
 
     /**
-     * Get the number of channels of audio that will be available.
+     * 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.
@@ -193,6 +203,8 @@
     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;
@@ -201,7 +213,6 @@
     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;
@@ -225,6 +236,7 @@
     }
 
     void clearRingBuffers(bool haveLock = false, size_t count = 0);
+    void unifyRingBuffers();
 
     class TimeStretcherData
     {
--- a/audioio/AudioGenerator.cpp	Fri Feb 17 18:04:26 2006 +0000
+++ b/audioio/AudioGenerator.cpp	Fri Feb 17 18:11:08 2006 +0000
@@ -41,6 +41,18 @@
 }
 
 bool
+AudioGenerator::canPlay(const Model *model)
+{
+    if (dynamic_cast<const DenseTimeValueModel *>(model) ||
+	dynamic_cast<const SparseOneDimensionalModel *>(model) ||
+	dynamic_cast<const NoteModel *>(model)) {
+	return true;
+    } else {
+	return false;
+    }
+}
+
+bool
 AudioGenerator::addModel(Model *model)
 {
     if (m_sourceSampleRate == 0) {
@@ -176,6 +188,10 @@
 void
 AudioGenerator::setTargetChannelCount(size_t targetChannelCount)
 {
+    if (m_targetChannelCount == targetChannelCount) return;
+
+    std::cerr << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << std::endl;
+
     QMutexLocker locker(&m_mutex);
     m_targetChannelCount = targetChannelCount;
 
@@ -251,28 +267,47 @@
     }
     
     size_t got = 0;
+    size_t prevChannel = 999;
 
-    for (size_t c = 0; c < m_targetChannelCount && c < dtvm->getChannelCount(); ++c) {
+    for (size_t c = 0; c < m_targetChannelCount; ++c) {
 
-	if (startFrame >= fadeIn/2) {
-	    got = dtvm->getValues
-		(c, startFrame - fadeIn/2, startFrame + frames + fadeOut/2,
-		 channelBuffer);
-	} else {
-	    size_t missing = fadeIn/2 - startFrame;
-	    got = dtvm->getValues
-		(c, 0, startFrame + frames + fadeOut/2,
-		 channelBuffer + missing);
-	}	    
+	size_t sourceChannel = (c % dtvm->getChannelCount());
+
+//	std::cerr << "mixing channel " << c << " from source channel " << sourceChannel << std::endl;
+
+	float channelGain = gain;
+	if (pan != 0.0) {
+	    if (c == 0) {
+		if (pan > 0.0) channelGain *= 1.0 - pan;
+	    } else {
+		if (pan < 0.0) channelGain *= pan + 1.0;
+	    }
+	}
+
+	if (prevChannel != sourceChannel) {
+	    if (startFrame >= fadeIn/2) {
+		got = dtvm->getValues
+		    (sourceChannel,
+		     startFrame - fadeIn/2, startFrame + frames + fadeOut/2,
+		     channelBuffer);
+	    } else {
+		size_t missing = fadeIn/2 - startFrame;
+		got = dtvm->getValues
+		    (sourceChannel,
+		     0, startFrame + frames + fadeOut/2,
+		     channelBuffer + missing);
+	    }	    
+	}
+	prevChannel = sourceChannel;
 
 	for (size_t i = 0; i < fadeIn/2; ++i) {
 	    float *back = buffer[c];
 	    back -= fadeIn/2;
-	    back[i] += (gain * channelBuffer[i] * i) / fadeIn;
+	    back[i] += (channelGain * channelBuffer[i] * i) / fadeIn;
 	}
 
 	for (size_t i = 0; i < frames + fadeOut/2; ++i) {
-	    float mult = gain;
+	    float mult = channelGain;
 	    if (i < fadeIn/2) {
 		mult = (mult * i) / fadeIn;
 	    }
@@ -402,13 +437,25 @@
 	plugin->run(blockTime);
 	float **outs = plugin->getAudioOutputBuffers();
 
-	for (size_t c = 0; c < m_targetChannelCount && c < plugin->getAudioOutputCount(); ++c) {
+	for (size_t c = 0; c < m_targetChannelCount; ++c) {
 #ifdef DEBUG_AUDIO_GENERATOR
 	    std::cout << "mixModel [sparse]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl;
 #endif
 
+	    size_t sourceChannel = (c % plugin->getAudioOutputCount());
+
+	    float channelGain = gain;
+	    if (pan != 0.0) {
+		if (c == 0) {
+		    if (pan > 0.0) channelGain *= 1.0 - pan;
+		} else {
+		    if (pan < 0.0) channelGain *= pan + 1.0;
+		}
+	    }
+
 	    for (size_t j = 0; j < m_pluginBlockSize; ++j) {
-		buffer[c][i * m_pluginBlockSize + j] += gain * outs[c][j];
+		buffer[c][i * m_pluginBlockSize + j] +=
+		    channelGain * outs[sourceChannel][j];
 	    }
 	}
     }
@@ -536,13 +583,25 @@
 	plugin->run(blockTime);
 	float **outs = plugin->getAudioOutputBuffers();
 
-	for (size_t c = 0; c < m_targetChannelCount && c < plugin->getAudioOutputCount(); ++c) {
+	for (size_t c = 0; c < m_targetChannelCount; ++c) {
 #ifdef DEBUG_AUDIO_GENERATOR
 	    std::cout << "mixModel [note]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl;
 #endif
 
+	    size_t sourceChannel = (c % plugin->getAudioOutputCount());
+
+	    float channelGain = gain;
+	    if (pan != 0.0) {
+		if (c == 0) {
+		    if (pan > 0.0) channelGain *= 1.0 - pan;
+		} else {
+		    if (pan < 0.0) channelGain *= pan + 1.0;
+		}
+	    }
+
 	    for (size_t j = 0; j < m_pluginBlockSize; ++j) {
-		buffer[c][i * m_pluginBlockSize + j] += gain * outs[c][j];
+		buffer[c][i * m_pluginBlockSize + j] += 
+		    channelGain * outs[sourceChannel][j];
 	    }
 	}
     }
--- a/audioio/AudioGenerator.h	Fri Feb 17 18:04:26 2006 +0000
+++ b/audioio/AudioGenerator.h	Fri Feb 17 18:11:08 2006 +0000
@@ -29,10 +29,19 @@
     virtual ~AudioGenerator();
 
     /**
-     * Add a data model to be played from and initialise any
-     * necessary audio generation code.
-     * Returns true if the model is of a type that we know how to play.
-     * (The model will be added regardless.)
+     * Return true if the given model is of a type that we generally
+     * know how to play.  This doesn't guarantee that a specific
+     * AudioGenerator will actually produce sounds for it (for
+     * example, it may turn out that a vital plugin is missing).
+     */
+    static bool canPlay(const Model *model);
+
+    /**
+     * Add a data model to be played from and initialise any necessary
+     * audio generation code.  Returns true if the model will be
+     * played.  (The return value test here is stricter than that for
+     * canPlay, above.)  The model will be added regardless of the
+     * return value.
      */
     virtual bool addModel(Model *model);
 
--- a/audioio/AudioJACKTarget.cpp	Fri Feb 17 18:04:26 2006 +0000
+++ b/audioio/AudioJACKTarget.cpp	Fri Feb 17 18:11:08 2006 +0000
@@ -83,6 +83,9 @@
 
     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;