changeset 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 2edc0757ca75
files audioio/AudioCallbackPlaySource.cpp audioio/AudioCallbackPlaySource.h audioio/AudioGenerator.cpp audioio/AudioGenerator.h
diffstat 4 files changed, 151 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/audioio/AudioCallbackPlaySource.cpp	Tue Jan 24 16:20:58 2006 +0000
+++ b/audioio/AudioCallbackPlaySource.cpp	Wed Jan 25 17:46:28 2006 +0000
@@ -35,6 +35,7 @@
     m_playing(false),
     m_exiting(false),
     m_bufferedToFrame(0),
+    m_lastModelEndFrame(0),
     m_outputLeft(0.0),
     m_outputRight(0.0),
     m_slowdownCounter(0),
@@ -75,6 +76,9 @@
     m_mutex.lock();
 
     m_models.insert(model);
+    if (model->getEndFrame() > m_lastModelEndFrame) {
+	m_lastModelEndFrame = model->getEndFrame();
+    }
 
     bool buffersChanged = false, srChanged = false;
 
@@ -164,6 +168,13 @@
 	m_sourceSampleRate = 0;
     }
 
+    size_t lastEnd = 0;
+    for (std::set<Model *>::const_iterator i = m_models.begin();
+	 i != m_models.end(); ++i) {
+	if ((*i)->getEndFrame() > lastEnd) lastEnd = (*i)->getEndFrame();
+    }
+    m_lastModelEndFrame = lastEnd;
+
     m_audioGenerator->removeModel(model);
 
     m_mutex.unlock();
@@ -181,6 +192,8 @@
 	m_converter = 0;
     }
 
+    m_lastModelEndFrame = 0;
+
     m_audioGenerator->clearModels();
 
     m_sourceSampleRate = 0;
@@ -191,6 +204,22 @@
 void
 AudioCallbackPlaySource::play(size_t startFrame)
 {
+    if (m_viewManager->getPlaySelectionMode()) {
+	ViewManager::SelectionList selections = m_viewManager->getSelections();
+	ViewManager::SelectionList::iterator i = selections.begin();
+	if (i != selections.end()) {
+	    if (startFrame < i->getStartFrame()) {
+		startFrame = i->getStartFrame();
+	    } else {
+		ViewManager::SelectionList::iterator j = selections.end();
+		--j;
+		if (startFrame >= j->getEndFrame()) {
+		    startFrame = i->getStartFrame();
+		}
+	    }
+	}
+    }
+
     // 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.
@@ -211,6 +240,7 @@
 
     m_playing = true;
     m_condition.wakeAll();
+    emit playStatusChanged(m_playing);
 }
 
 void
@@ -218,6 +248,7 @@
 {
     m_playing = false;
     m_condition.wakeAll();
+    emit playStatusChanged(m_playing);
 }
 
 void
@@ -229,17 +260,13 @@
 	    getRingBuffer(c).reset();
 	}
 	m_mutex.unlock();
+	m_condition.wakeAll();
     }
 }
 
 void
 AudioCallbackPlaySource::playLoopModeChanged()
 {
-    m_mutex.lock();
-    for (size_t c = 0; c < m_bufferCount; ++c) {
-	getRingBuffer(c).reset();
-    }
-    m_mutex.unlock();
 }
 
 void
@@ -251,6 +278,7 @@
 	    getRingBuffer(c).reset();
 	}
 	m_mutex.unlock();
+	m_condition.wakeAll();
     }
 }
 
@@ -317,7 +345,15 @@
 
     size_t framePlaying = bufferedFrame;
     if (framePlaying > latency) framePlaying -= latency;
-    else framePlaying = 0;
+    else {
+	//!!! Not right
+	if (m_viewManager->getPlayLoopMode() &&
+	    !m_viewManager->getPlaySelectionMode()) {
+	    framePlaying += m_lastModelEndFrame;
+	    if (framePlaying > latency) framePlaying -= latency;
+	    else framePlaying = 0;
+	}
+    }
 
     if (!m_viewManager->getPlaySelectionMode()) {
 	return framePlaying;
@@ -330,26 +366,41 @@
 
     ViewManager::SelectionList::const_iterator i;
 
+    i = selections.begin();
+    size_t rangeStart = i->getStartFrame();
+
+    i = selections.end();
+    --i;
+    size_t rangeEnd = i->getEndFrame();
+
     for (i = selections.begin(); i != selections.end(); ++i) {
 	if (i->contains(bufferedFrame)) break;
     }
 
     size_t f = bufferedFrame;
 
-    std::cerr << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << std::endl;
+//    std::cerr << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << ", rangeEnd=" << rangeEnd << std::endl;
 
     if (i == selections.end()) {
 	--i;
 	if (i->getEndFrame() + latency < f) {
-	    return framePlaying;
+//    std::cerr << "framePlaying = " << framePlaying << ", rangeEnd = " << rangeEnd << std::endl;
+
+	    if (!m_viewManager->getPlayLoopMode() && (framePlaying > rangeEnd)) {
+//		std::cerr << "STOPPING" << std::endl;
+		stop();
+		return rangeEnd;
+	    } else {
+		return framePlaying;
+	    }
 	} else {
-	    std::cerr << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl;
+//	    std::cerr << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl;
 	    latency -= (f - i->getEndFrame());
 	    f = i->getEndFrame();
 	}
     }
 
-    std::cerr << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl;
+//    std::cerr << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl;
 
     while (latency > 0) {
 	size_t offset = f - i->getStartFrame();
@@ -615,7 +666,7 @@
     }
     
     if (space == 0) return;
-    
+
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
     std::cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << std::endl;
 #endif
@@ -818,6 +869,8 @@
 	chunkSize = count - processed;
 	nextChunkStart = chunkStart + chunkSize;
 
+	size_t fadeIn = 0, fadeOut = 0;
+
 	if (useSelection) {
 	    
 	    Selection selection =
@@ -827,6 +880,7 @@
 		if (m_viewManager->getPlayLoopMode()) {
 		    selection = *m_viewManager->getSelections().begin();
 		    chunkStart = selection.getStartFrame();
+		    fadeIn = 50;
 		}
 	    }
 
@@ -839,13 +893,29 @@
 
 		if (chunkStart < selection.getStartFrame()) {
 		    chunkStart = selection.getStartFrame();
+		    fadeIn = 50;
 		}
 
-		nextChunkStart = std::min(chunkStart + chunkSize,
-					  selection.getEndFrame());
+		nextChunkStart = chunkStart + chunkSize;
+
+		if (nextChunkStart > selection.getEndFrame()) {
+		    nextChunkStart = selection.getEndFrame();
+		    fadeOut = 50;
+		}
 
 		chunkSize = nextChunkStart - chunkStart;
 	    }
+	
+	} else if (m_viewManager->getPlayLoopMode() &&
+		   m_lastModelEndFrame > 0) {
+
+	    if (chunkStart >= m_lastModelEndFrame) {
+		chunkStart = 0;
+	    }
+	    if (chunkSize > m_lastModelEndFrame - chunkStart) {
+		chunkSize = m_lastModelEndFrame - chunkStart;
+	    }
+	    nextChunkStart = chunkStart + chunkSize;
 	}
 	
 	if (!chunkSize) {
@@ -865,11 +935,32 @@
 
 	size_t got = 0;
 
+	if (chunkSize < 100) {
+	    fadeIn = 0;
+	    fadeOut = 0;
+	} else if (chunkSize < 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) * 2 < fadeOut) {
+		fadeOut = (count - processed) * 2;
+	    }
+	}
+
 	for (std::set<Model *>::iterator mi = m_models.begin();
 	     mi != m_models.end(); ++mi) {
 	    
 	    got = m_audioGenerator->mixModel(*mi, chunkStart, 
-					     chunkSize, chunkBufferPtrs);
+					     chunkSize, chunkBufferPtrs,
+					     fadeIn, fadeOut);
 	}
 
 	for (size_t c = 0; c < channels; ++c) {
@@ -911,12 +1002,13 @@
 	}
 
 	if (!s.m_playing) ms *= 10;
+	ms = ms / 8;
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
-	std::cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms/4 << "ms..." << std::endl;
+	std::cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms << "ms..." << std::endl;
 #endif
 
-	s.m_condition.wait(&s.m_mutex, size_t(ms / 4));
+	s.m_condition.wait(&s.m_mutex, size_t(ms));
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
 	std::cout << "AudioCallbackPlaySourceFillThread: awoken" << std::endl;
--- a/audioio/AudioCallbackPlaySource.h	Tue Jan 24 16:20:58 2006 +0000
+++ b/audioio/AudioCallbackPlaySource.h	Wed Jan 25 17:46:28 2006 +0000
@@ -164,6 +164,8 @@
 signals:
     void modelReplaced();
 
+    void playStatusChanged(bool isPlaying);
+
     /// Just a warning
     void sampleRateMismatch(size_t requested, size_t available);
 
@@ -186,6 +188,7 @@
     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;
@@ -222,7 +225,10 @@
     TimeStretcherData *m_timeStretcher;
     Scavenger<TimeStretcherData> m_timeStretcherScavenger;
 
-    void fillBuffers(); // Called from fill thread, m_playing true, mutex held
+    // 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
--- a/audioio/AudioGenerator.cpp	Tue Jan 24 16:20:58 2006 +0000
+++ b/audioio/AudioGenerator.cpp	Wed Jan 25 17:46:28 2006 +0000
@@ -150,7 +150,7 @@
 
 size_t
 AudioGenerator::mixModel(Model *model, size_t startFrame, size_t frameCount,
-			 float **buffer)
+			 float **buffer, size_t fadeIn, size_t fadeOut)
 {
     if (m_sourceSampleRate == 0) {
 	std::cerr << "WARNING: AudioGenerator::mixModel: No base source sample rate available" << std::endl;
@@ -169,14 +169,14 @@
     DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
     if (dtvm) {
 	return mixDenseTimeValueModel(dtvm, startFrame, frameCount,
-				      buffer, gain, pan);
+				      buffer, gain, pan, fadeIn, fadeOut);
     }
 
     SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
 	(model);
     if (sodm) {
 	return mixSparseOneDimensionalModel(sodm, startFrame, frameCount,
-					    buffer, gain, pan);
+					    buffer, gain, pan, fadeIn, fadeOut);
     }
 
     return frameCount;
@@ -185,23 +185,43 @@
 size_t
 AudioGenerator::mixDenseTimeValueModel(DenseTimeValueModel *dtvm,
 				       size_t startFrame, size_t frames,
-				       float **buffer, float gain, float pan)
+				       float **buffer, float gain, float pan,
+				       size_t fadeIn, size_t fadeOut)
 {
     static float *channelBuffer = 0;
     static size_t channelBufSiz = 0;
     
-    if (channelBufSiz < frames) {
+    size_t totalFrames = frames + fadeIn/2 + fadeOut/2;
+
+    if (channelBufSiz < totalFrames) {
 	delete[] channelBuffer;
-	channelBuffer = new float[frames];
-	channelBufSiz = frames;
+	channelBuffer = new float[totalFrames];
+	channelBufSiz = totalFrames;
     }
     
     size_t got = 0;
 
     for (size_t c = 0; c < m_targetChannelCount && c < dtvm->getChannelCount(); ++c) {
-	got = dtvm->getValues(c, startFrame, startFrame + frames, channelBuffer);
-	for (size_t i = 0; i < frames; ++i) {
-	    buffer[c][i] += gain * channelBuffer[i];
+
+	got = dtvm->getValues
+	    (c, startFrame - fadeIn/2, startFrame + frames + fadeOut/2,
+	     channelBuffer);
+
+	for (size_t i = 0; i < fadeIn/2; ++i) {
+	    float *back = buffer[c];
+	    back -= fadeIn/2;
+	    back[i] += (gain * channelBuffer[i] * i) / fadeIn;
+	}
+
+	for (size_t i = 0; i < frames + fadeOut/2; ++i) {
+	    float mult = gain;
+	    if (i < fadeIn/2) {
+		mult = (mult * i) / fadeIn;
+	    }
+	    if (i > frames - fadeOut/2) {
+		mult = (mult * ((frames + fadeOut/2) - i)) / fadeOut;
+	    }
+	    buffer[c][i] += mult * channelBuffer[i];
 	}
     }
 
@@ -211,7 +231,9 @@
 size_t
 AudioGenerator::mixSparseOneDimensionalModel(SparseOneDimensionalModel *sodm,
 					     size_t startFrame, size_t frames,
-					     float **buffer, float gain, float pan)
+					     float **buffer, float gain, float pan,
+					     size_t /* fadeIn */,
+					     size_t /* fadeOut */)
 {
     RealTimePluginInstance *plugin = m_synthMap[sodm];
     if (!plugin) return 0;
--- a/audioio/AudioGenerator.h	Tue Jan 24 16:20:58 2006 +0000
+++ b/audioio/AudioGenerator.h	Wed Jan 25 17:46:28 2006 +0000
@@ -63,7 +63,7 @@
      * Mix a single model into an output buffer.
      */
     virtual size_t mixModel(Model *model, size_t startFrame, size_t frameCount,
-			    float **buffer);
+			    float **buffer, size_t fadeIn = 0, size_t fadeOut = 0);
 
 protected:
     ViewManager *m_viewManager;
@@ -93,11 +93,11 @@
 
     virtual size_t mixDenseTimeValueModel
     (DenseTimeValueModel *model, size_t startFrame, size_t frameCount,
-     float **buffer, float gain, float pan);
+     float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut);
 
     virtual size_t mixSparseOneDimensionalModel
     (SparseOneDimensionalModel *model, size_t startFrame, size_t frameCount,
-     float **buffer, float gain, float pan);
+     float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut);
 
     static const size_t m_pluginBlockSize;
 };