changeset 37:21d061e66177

* Make the frequency estimation mode in the spectrogram layer actually useful, and make sure it gets mostly the right results... Still some tidying to do in here.
author Chris Cannam
date Wed, 22 Feb 2006 17:45:18 +0000
parents c28ebb4ba4de
children beb801473743
files layer/LayerFactory.cpp layer/LayerFactory.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h widgets/Pane.cpp
diffstat 5 files changed, 390 insertions(+), 312 deletions(-) [+]
line wrap: on
line diff
--- a/layer/LayerFactory.cpp	Mon Feb 20 17:23:40 2006 +0000
+++ b/layer/LayerFactory.cpp	Wed Feb 22 17:45:18 2006 +0000
@@ -58,6 +58,10 @@
 	// that should be encoded in its name
 	return Layer::tr("Spectrogram");
 
+    case PeakFrequencySpectrogram:
+	// likewise
+	return Layer::tr("Spectrogram");
+
     default: break;
     }
 
@@ -76,6 +80,7 @@
     if (dynamic_cast<DenseTimeValueModel *>(model)) {
 	types.insert(Spectrogram);
 	types.insert(MelodicRangeSpectrogram);
+	types.insert(PeakFrequencySpectrogram);
     }
 
     if (dynamic_cast<RangeSummarisableTimeValueModel *>(model)) {
@@ -272,6 +277,11 @@
 	static_cast<SpectrogramLayer *>(layer)->setChannel(channel);
 	break;
 
+    case PeakFrequencySpectrogram: 
+	layer = new SpectrogramLayer(view, SpectrogramLayer::MelodicPeaks);
+	static_cast<SpectrogramLayer *>(layer)->setChannel(channel);
+	break;
+
     default: break;
     }
 
--- a/layer/LayerFactory.h	Mon Feb 20 17:23:40 2006 +0000
+++ b/layer/LayerFactory.h	Wed Feb 22 17:45:18 2006 +0000
@@ -34,6 +34,7 @@
 
 	// Layers with different initial parameters
 	MelodicRangeSpectrogram,
+	PeakFrequencySpectrogram,
 
 	// Not-a-layer-type
 	UnknownLayer = 255
--- a/layer/SpectrogramLayer.cpp	Mon Feb 20 17:23:40 2006 +0000
+++ b/layer/SpectrogramLayer.cpp	Wed Feb 22 17:45:18 2006 +0000
@@ -39,12 +39,14 @@
     m_windowType(HanningWindow),
     m_windowOverlap(50),
     m_gain(1.0),
+    m_threshold(0.0),
     m_colourRotation(0),
+    m_minFrequency(0),
     m_maxFrequency(8000),
     m_colourScale(dBColourScale),
     m_colourScheme(DefaultColours),
     m_frequencyScale(LinearFrequencyScale),
-    m_frequencyAdjustment(RawFrequency),
+    m_binDisplay(AllBins),
     m_normalizeColumns(false),
     m_cache(0),
     m_phaseAdjustCache(0),
@@ -62,6 +64,16 @@
 	setWindowType(ParzenWindow);
 	setMaxFrequency(1000);
 	setColourScale(LinearColourScale);
+    } else if (config == MelodicPeaks) {
+	setWindowSize(4096);
+	setWindowOverlap(90);
+	setWindowType(BlackmanWindow);
+	setMaxFrequency(1500);
+	setMinFrequency(40);
+	setFrequencyScale(LogFrequencyScale);
+	setColourScale(dBColourScale);
+	setBinDisplay(PeakFrequencies);
+	setNormalizeColumns(true);
     }
 
     if (m_view) m_view->setLightBackground(false);
@@ -126,11 +138,13 @@
     list.push_back(tr("Window Size"));
     list.push_back(tr("Window Overlap"));
     list.push_back(tr("Normalize"));
+    list.push_back(tr("Bin Display"));
+    list.push_back(tr("Threshold"));
     list.push_back(tr("Gain"));
     list.push_back(tr("Colour Rotation"));
+    list.push_back(tr("Min Frequency"));
     list.push_back(tr("Max Frequency"));
     list.push_back(tr("Frequency Scale"));
-    list.push_back(tr("Frequency Adjustment"));
     return list;
 }
 
@@ -140,6 +154,7 @@
     if (name == tr("Gain")) return RangeProperty;
     if (name == tr("Colour Rotation")) return RangeProperty;
     if (name == tr("Normalize")) return ToggleProperty;
+    if (name == tr("Threshold")) return RangeProperty;
     return ValueProperty;
 }
 
@@ -152,11 +167,14 @@
     if (name == tr("Colour") ||
 	name == tr("Colour Rotation")) return tr("Colour");
     if (name == tr("Gain") ||
+	name == tr("Threshold") ||
 	name == tr("Normalize") ||
+	name == tr("Bin Display") ||
 	name == tr("Colour Scale")) return tr("Scale");
     if (name == tr("Max Frequency") ||
+	name == tr("Min Frequency") ||
 	name == tr("Frequency Scale") ||
-	name == tr("Frequency Adjustment")) return tr("Frequency");
+	name == tr("Frequency Adjustment")) return tr("Range");
     return QString();
 }
 
@@ -179,6 +197,15 @@
 	if (deft < *min) deft = *min;
 	if (deft > *max) deft = *max;
 
+    } else if (name == tr("Threshold")) {
+
+	*min = -50;
+	*max = 0;
+
+	deft = lrintf(AudioLevel::multiplier_to_dB(m_threshold));
+	if (deft < *min) deft = *min;
+	if (deft > *max) deft = *max;
+
     } else if (name == tr("Colour Rotation")) {
 
 	*min = 0;
@@ -224,6 +251,24 @@
 	deft = m_windowOverlap / 25;
 	if (m_windowOverlap == 90) deft = 4;
     
+    } else if (name == tr("Min Frequency")) {
+
+	*min = 0;
+	*max = 9;
+
+	switch (m_minFrequency) {
+	case 0: default: deft = 0; break;
+	case 10: deft = 1; break;
+	case 20: deft = 2; break;
+	case 40: deft = 3; break;
+	case 100: deft = 4; break;
+	case 250: deft = 5; break;
+	case 500: deft = 6; break;
+	case 1000: deft = 7; break;
+	case 4000: deft = 8; break;
+	case 10000: deft = 9; break;
+	}
+    
     } else if (name == tr("Max Frequency")) {
 
 	*min = 0;
@@ -248,11 +293,11 @@
 	*max = 1;
 	deft = (int)m_frequencyScale;
 
-    } else if (name == tr("Frequency Adjustment")) {
+    } else if (name == tr("Bin Display")) {
 
 	*min = 0;
 	*max = 2;
-	deft = (int)m_frequencyAdjustment;
+	deft = (int)m_binDisplay;
 
     } else if (name == tr("Normalize")) {
 	
@@ -283,9 +328,9 @@
     if (name == tr("Colour Scale")) {
 	switch (value) {
 	default:
-	case 0: return tr("Level Linear");
-	case 1: return tr("Level Meter");
-	case 2: return tr("Level dB");
+	case 0: return tr("Linear");
+	case 1: return tr("Meter");
+	case 2: return tr("dB");
 	case 3: return tr("Phase");
 	}
     }
@@ -314,6 +359,21 @@
 	case 4: return tr("90%");
 	}
     }
+    if (name == tr("Min Frequency")) {
+	switch (value) {
+	default:
+	case 0: return tr("None");
+	case 1: return tr("10 Hz");
+	case 2: return tr("20 Hz");
+	case 3: return tr("40 Hz");
+	case 4: return tr("100 Hz");
+	case 5: return tr("250 Hz");
+	case 6: return tr("500 Hz");
+	case 7: return tr("1 KHz");
+	case 8: return tr("4 KHz");
+	case 9: return tr("10 KHz");
+	}
+    }
     if (name == tr("Max Frequency")) {
 	switch (value) {
 	default:
@@ -336,12 +396,12 @@
 	case 1: return tr("Log");
 	}
     }
-    if (name == tr("Frequency Adjustment")) {
+    if (name == tr("Bin Display")) {
 	switch (value) {
 	default:
-	case 0: return tr("Bins");
-	case 1: return tr("Pitches");
-	case 2: return tr("Peaks");
+	case 0: return tr("All Bins");
+	case 1: return tr("Peak Bins");
+	case 2: return tr("Frequencies");
 	}
     }
     return tr("<unknown>");
@@ -352,6 +412,9 @@
 {
     if (name == tr("Gain")) {
 	setGain(pow(10, float(value)/20.0));
+    } else if (name == tr("Threshold")) {
+	if (value == -50) setThreshold(0.0);
+	else setThreshold(AudioLevel::dB_to_multiplier(value));
     } else if (name == tr("Colour Rotation")) {
 	setColourRotation(value);
     } else if (name == tr("Colour")) {
@@ -372,6 +435,20 @@
     } else if (name == tr("Window Overlap")) {
 	if (value == 4) setWindowOverlap(90);
 	else setWindowOverlap(25 * value);
+    } else if (name == tr("Min Frequency")) {
+	switch (value) {
+	default:
+	case 0: setMinFrequency(0); break;
+	case 1: setMinFrequency(10); break;
+	case 2: setMinFrequency(20); break;
+	case 3: setMinFrequency(40); break;
+	case 4: setMinFrequency(100); break;
+	case 5: setMinFrequency(250); break;
+	case 6: setMinFrequency(500); break;
+	case 7: setMinFrequency(1000); break;
+	case 8: setMinFrequency(4000); break;
+	case 9: setMinFrequency(10000); break;
+	}
     } else if (name == tr("Max Frequency")) {
 	switch (value) {
 	case 0: setMaxFrequency(500); break;
@@ -400,12 +477,12 @@
 	case 0: setFrequencyScale(LinearFrequencyScale); break;
 	case 1: setFrequencyScale(LogFrequencyScale); break;
 	}
-    } else if (name == tr("Frequency Adjustment")) {
+    } else if (name == tr("Bin Display")) {
 	switch (value) {
 	default:
-	case 0: setFrequencyAdjustment(RawFrequency); break;
-	case 1: setFrequencyAdjustment(PhaseAdjustedFrequency); break;
-	case 2: setFrequencyAdjustment(PhaseAdjustedPeaks); break;
+	case 0: setBinDisplay(AllBins); break;
+	case 1: setBinDisplay(PeakBins); break;
+	case 2: setBinDisplay(PeakFrequencies); break;
 	}
     } else if (name == "Normalize") {
 	setNormalizeColumns(value ? true : false);
@@ -533,6 +610,52 @@
 }
 
 void
+SpectrogramLayer::setThreshold(float threshold)
+{
+    if (m_threshold == threshold) return; //!!! inadequate for floats!
+
+    m_mutex.lock();
+    m_cacheInvalid = true;
+    m_pixmapCacheInvalid = true;
+    
+    m_threshold = threshold;
+    
+    m_mutex.unlock();
+
+    emit layerParametersChanged();
+
+    fillCache();
+}
+
+float
+SpectrogramLayer::getThreshold() const
+{
+    return m_threshold;
+}
+
+void
+SpectrogramLayer::setMinFrequency(size_t mf)
+{
+    if (m_minFrequency == mf) return;
+
+    m_mutex.lock();
+    // don't need to invalidate main cache here
+    m_pixmapCacheInvalid = true;
+    
+    m_minFrequency = mf;
+    
+    m_mutex.unlock();
+
+    emit layerParametersChanged();
+}
+
+size_t
+SpectrogramLayer::getMinFrequency() const
+{
+    return m_minFrequency;
+}
+
+void
 SpectrogramLayer::setMaxFrequency(size_t mf)
 {
     if (m_maxFrequency == mf) return;
@@ -645,16 +768,16 @@
 }
 
 void
-SpectrogramLayer::setFrequencyAdjustment(FrequencyAdjustment frequencyAdjustment)
+SpectrogramLayer::setBinDisplay(BinDisplay binDisplay)
 {
-    if (m_frequencyAdjustment == frequencyAdjustment) return;
+    if (m_binDisplay == binDisplay) return;
 
     m_mutex.lock();
 
     m_cacheInvalid = true;
     m_pixmapCacheInvalid = true;
     
-    m_frequencyAdjustment = frequencyAdjustment;
+    m_binDisplay = binDisplay;
     
     m_mutex.unlock();
 
@@ -663,10 +786,10 @@
     emit layerParametersChanged();
 }
 
-SpectrogramLayer::FrequencyAdjustment
-SpectrogramLayer::getFrequencyAdjustment() const
+SpectrogramLayer::BinDisplay
+SpectrogramLayer::getBinDisplay() const
 {
-    return m_frequencyAdjustment;
+    return m_binDisplay;
 }
 
 void
@@ -808,7 +931,7 @@
 
     int formerRotation = m_colourRotation;
 
-    m_cache->setColour(0, Qt::white);
+    m_cache->setColour(NO_VALUE, Qt::white);
 
     for (int pixel = 1; pixel < 256; ++pixel) {
 
@@ -866,7 +989,7 @@
 
     QColor newPixels[256];
 
-    newPixels[0] = m_cache->getColour(0);
+    newPixels[NO_VALUE] = m_cache->getColour(NO_VALUE);
 
     for (int pixel = 1; pixel < 256; ++pixel) {
 	int target = pixel + distance;
@@ -891,14 +1014,10 @@
 {
     static std::vector<double> storedPhase;
 
-    bool phaseAdjust =
-	(m_frequencyAdjustment == PhaseAdjustedFrequency ||
-	 m_frequencyAdjustment == PhaseAdjustedPeaks);
+    bool phaseAdjust = (m_binDisplay == PeakFrequencies);
     bool haveStoredPhase = true;
     size_t sampleRate = 0;
 
-    static int counter = 0;
-
     if (phaseAdjust) {
 	if (resetStoredPhase || (storedPhase.size() != windowSize / 2)) {
 	    haveStoredPhase = false;
@@ -906,9 +1025,7 @@
 	    for (size_t i = 0; i < windowSize / 2; ++i) {
 		storedPhase.push_back(0.0);
 	    }
-	    counter = 0;
 	}
-	++counter;
 	sampleRate = m_model->getSampleRate();
     }
 
@@ -939,6 +1056,15 @@
 	}
     }
 
+    if (m_channel == -1) {
+	int channels = m_model->getChannelCount();
+	if (channels > 1) {
+	    for (size_t i = 0; i < windowSize; ++i) {
+		input[i] /= channels;
+	    }
+	}
+    }
+
     windower.cut(input);
 
     for (size_t i = 0; i < windowSize/2; ++i) {
@@ -952,8 +1078,8 @@
     bool interrupted = false;
 
     double prevMag = 0.0;
-
     double maxMag = 0.0;
+
     if (m_normalizeColumns) {
 	for (size_t i = 0; i < windowSize/2; ++i) {
 	    double mag = sqrt(output[i][0] * output[i][0] +
@@ -963,6 +1089,11 @@
 	}
     }
 
+    if (maxMag == 0.0) maxMag = 1.0;
+
+    bool peaksOnly = (m_binDisplay == PeakBins ||
+		      m_binDisplay == PeakFrequencies);
+
     for (size_t i = 0; i < windowSize / 2; ++i) {
 
 	int value = 0;
@@ -970,120 +1101,96 @@
 
 	double mag = sqrt(output[i][0] * output[i][0] +
 			  output[i][1] * output[i][1]);
-	mag /= windowSize / 2;
-
-	if (m_normalizeColumns && maxMag > 0.0) {
-	    mag /= maxMag;
+
+	mag /= (windowSize / 2);
+	if (m_normalizeColumns) mag /= maxMag;
+
+	bool showThis = true;
+
+	if (peaksOnly) {
+	    if (mag < prevMag) showThis = false;
+	    else {
+		double nextMag = 0.0;
+		if (i < windowSize / 2 - 1) {
+		    nextMag = sqrt(output[i+1][0] * output[i+1][0] +
+				   output[i+1][1] * output[i+1][1]);
+		    nextMag /= windowSize / 2;
+		    if (m_normalizeColumns) nextMag /= maxMag;
+		}
+		if (mag < nextMag) showThis = false;
+	    }
+	    prevMag = mag;
 	}
 
+	if (mag < m_threshold) showThis = false;
+
 	if (phaseAdjust || (m_colourScale == PhaseColourScale)) {
-
-//	    phase = atan2(-output[i][1], output[i][0]);
-//	    phase = atan2(output[i][1], output[i][0]);
-	    phase = atan2(output[i][0], output[i][1]);
-//	    phase = MathUtilities::princarg(phase);
+	    phase = atan2(output[i][1], output[i][0]);
+	    phase = MathUtilities::princarg(phase);
 	}	    
 
-	if (phaseAdjust && m_phaseAdjustCache && haveStoredPhase) {
-
-	    bool peak = true;
-//	    if (m_frequencyAdjustment == PhaseAdjustedPeaks) {
-	    if (true) { //!!!
-		if (mag < prevMag) peak = false;
-		else {
-		    double nextMag = 0.0;
-		    if (i < windowSize / 2 - 1) {
-			nextMag = sqrt(output[i+1][0] * output[i+1][0] +
-				       output[i+1][1] * output[i+1][1]);
-			nextMag /= windowSize / 2;
-			if (m_normalizeColumns && maxMag > 0.0) {
-			    nextMag /= maxMag;
-			}
-		    }
-		    if (mag < nextMag) peak = false;
-		}
-		prevMag = mag;
-	    }
-
-	    if (!peak) {
+	if (phaseAdjust && m_phaseAdjustCache) {
+	    m_phaseAdjustCache->setValueAt(column, i, 0);
+	}
+
+	if (phaseAdjust && m_phaseAdjustCache && haveStoredPhase && showThis) {
+
+	    double freq = (double(i) * sampleRate) / m_windowSize;
+	    double prevPhase = storedPhase[i];
+
+	    // At frequency f, phase shift of 2pi (one cycle) happens in 1/f sec.
+	    // At hopsize h and sample rate sr, one hop happens in h/sr sec.
+	    // At window size w, for bin i, f is i*sr/w.
+	    // thus 2pi phase shift happens in w/(b*sr) sec.
+	    // We need to know what phase shift we expect from h/sr sec.
+	    // -> 2pi * ((h/sr) / (w/(i*sr)))
+	    //  = 2pi * ((h * i * sr) / (w * sr))
+	    //  = 2pi * (h * i) / w.
+
+	    double expectedPhase =
+		prevPhase + (2.0 * M_PI * i * increment) / m_windowSize;
+
+	    double phaseError = MathUtilities::princarg(phase - expectedPhase);
+	    
+	    if (fabs(phaseError) < (1.1 * (increment * M_PI) / m_windowSize)) {
+
+		// The new frequency estimate based on the phase error
+		// resulting from assuming the "native" frequency of this bin
+
+		double newFreq =
+		    (sampleRate *
+		     (expectedPhase + phaseError - prevPhase)) /
+		    (2 * M_PI * increment);
+
+		// Convert the frequency difference to an unsigned char
+		// for storage in the phase adjust cache
+		
+		double binRange = (double(i + 1) * sampleRate) / windowSize - freq;
+	    
+		int offset = lrint(((newFreq - freq) / binRange) * 100);
+
 		if (m_cacheInvalid || m_exiting) {
 		    interrupted = true;
 		    break;
 		}
-		m_phaseAdjustCache->setValueAt(column, i, SCHAR_MIN);
-	    } else {
-
-	    if (i < 100 && counter == 10)  {
-	    
-//		if (counter == 10) {
-
-	    double freq = (double(i) * sampleRate) / m_windowSize;
-//	    std::cout << "\nbin = " << i << " initial estimate freq = " << freq
-//		      << " mag = " << mag << std::endl;
-
-	    double prevPhase = storedPhase[i];
-
-	    // If the frequency is 100Hz and the sample rate is
-	    // 10000Hz and we have 1000 samples (1/10sec) per bin,
-	    // then we expect phase 0 
-
-	    // 2pi happens in 1/freq sec
-	    // one hop happens in hopsize/sr sec
-	    // freq is bin*sr / windowsize
-	    // thus 2pi happens in windowsize/(bin*sr) sec
-	    
-	    // need to know what phase increment we expect from
-	    // hopsize/sr sec
-	    // must be 2pi * ((hopsize/sr) / (windowsize/(bin*sr)))
-	    // = 2pi * ((hopsize * bin * sr) / (windowsize * sr))
-	    // = 2pi * (hopsize * bin) / windowsize
-
-	    double expectedPhase =
-		prevPhase + (2.0 * M_PI * i * increment) / m_windowSize;
-
-	    double phaseError = MathUtilities::princarg(phase - expectedPhase);
-	    
-//	    if (fabs(phaseError) > (1.2 * (increment * M_PI) / m_windowSize)) {
-//		std::cout << "error > " << (1.2 * (increment * M_PI) / m_windowSize) << std::endl;
-//	    }// else {
-
-	    std::cout << "prev = " << prevPhase << ", phase = " << phase
-		      << ", exp = " << expectedPhase << " prin = " << MathUtilities::princarg(expectedPhase) << ", error = "
-		      << phaseError << " inc = " << increment << " i = " << i << ", pi = " << M_PI <<  std::endl;
-
-	    double newFreq =
-		(sampleRate *
-		 (expectedPhase + phaseError - prevPhase)) /
-		//(prevPhase - (expectedPhase + phaseError))) /
-		(2 * M_PI * increment);
-
-	    std::cout << freq << " (" << Pitch::getPitchLabelForFrequency(freq).toStdString() <<  ") -> " << newFreq << " (" << Pitch::getPitchLabelForFrequency(newFreq).toStdString() << ")" << std::endl;
-//	    }
-
-	    double binRange = (double(i + 1) * sampleRate) / windowSize - freq;
-	    
-	    int offset = lrint(((newFreq - freq) / binRange) * 10);//!!!
-
-	    if (m_cacheInvalid || m_exiting) {
-		interrupted = true;
-		break;
-	    }
-	    if (offset > SCHAR_MIN && offset <= SCHAR_MAX) {
-		signed char coff = offset;
-		m_phaseAdjustCache->setValueAt(column, i, (unsigned char)coff);
-	    } else {
-		m_phaseAdjustCache->setValueAt(column, i, 0);
-	    }
-	    }
-}
-
-	    storedPhase[i] = phase;
-	}
+
+		if (offset >= SCHAR_MIN && offset <= SCHAR_MAX) {
+		    signed char coff = offset;
+		    m_phaseAdjustCache->setValueAt(column, i, (unsigned char)coff);
+		} else {
+
+		    if (fabs(phaseError) < ((increment * M_PI) / m_windowSize)) {
+			std::cerr << "WARNING: Phase error " << phaseError << " ( < " << ((increment * M_PI) / m_windowSize) << ") but offset " << offset << " out of range" << std::endl;
+		    }
+		}
+	    }	
+	}	
+
+	if (phaseAdjust) storedPhase[i] = phase;
 
 	if (m_colourScale == PhaseColourScale) {
 
-	    phase = MathUtilities::princarg(phase);
-	    value = int((phase * 128 / M_PI) + 128);
+	    value = int((phase * 127 / M_PI) + 128);
 
 	} else {
 
@@ -1091,11 +1198,11 @@
 		
 	    default:
 	    case LinearColourScale:
-		value = int(mag * 50 * 256);
+		value = int(mag * 50 * 255) + 1;
 		break;
 		
 	    case MeterColourScale:
-		value = AudioLevel::multiplier_to_preview(mag * 50, 256);
+		value = AudioLevel::multiplier_to_preview(mag * 50, 255) + 1;
 	    break;
 
 	    case dBColourScale:
@@ -1103,11 +1210,11 @@
 		mag = (mag + 80.0) / 80.0;
 		if (mag < 0.0) mag = 0.0;
 		if (mag > 1.0) mag = 1.0;
-		value = int(mag * 256);
+		value = int(mag * 255) + 1;
 	    }
 	}
 
-	if (value > 254) value = 254;
+	if (value > UCHAR_MAX) value = UCHAR_MAX;
 	if (value < 0) value = 0;
 
 	if (m_cacheInvalid || m_exiting) {
@@ -1115,7 +1222,11 @@
 	    break;
 	}
 
-	m_cache->setValueAt(column, i, value + 1);
+	if (showThis) {
+	    m_cache->setValueAt(column, i, value);
+	} else {
+	    m_cache->setValueAt(column, i, NO_VALUE);
+	}
     }
 
     return !interrupted;
@@ -1140,6 +1251,7 @@
 void
 SpectrogramLayer::Cache::resize(size_t width, size_t height)
 {
+    std::cerr << "SpectrogramLayer::Cache[" << this << "]::resize(" << width << "x" << height << ")" << std::endl;
     m_values = (unsigned char *)
 	realloc(m_values, m_width * m_height * sizeof(unsigned char));
     if (!m_values) throw std::bad_alloc();
@@ -1187,6 +1299,7 @@
 void
 SpectrogramLayer::Cache::fill(unsigned char value)
 {
+    std::cerr << "SpectrogramLayer::Cache[" << this << "]::fill(" << value << ")" << std::endl;
     for (size_t i = 0; i < m_width * m_height; ++i) {
 	m_values[i] = value;
     }
@@ -1261,10 +1374,9 @@
 	    }
 
 	    m_layer.setCacheColourmap();
-	    m_layer.m_cache->fill(0);
-
-	    if (m_layer.m_frequencyAdjustment == PhaseAdjustedFrequency ||
-		m_layer.m_frequencyAdjustment == PhaseAdjustedPeaks) {
+	    m_layer.m_cache->fill(NO_VALUE);
+
+	    if (m_layer.m_binDisplay == PeakFrequencies) {
 		
 		if (!m_layer.m_phaseAdjustCache) {
 		    m_layer.m_phaseAdjustCache = new Cache(width, height);
@@ -1306,6 +1418,7 @@
 		std::cerr << "WARNING: fftw_plan_dft_r2c_1d(" << windowSize << ") failed!" << std::endl;
 		fftw_free(input);
 		fftw_free(output);
+		m_layer.m_mutex.lock();
 		continue;
 	    }
 
@@ -1331,17 +1444,18 @@
 			break;
 		    }
 
-		    if (++counter == updateAt) {
+		    if (++counter == updateAt || f == visibleEnd - 1) {
 			if (f < end) m_fillExtent = f;
 			m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
 							      float(end - start)));
 			counter = 0;
 		    }
 		}
+
+		m_layer.m_cachedInitialVisibleArea = true;
+		std::cerr << "SpectrogramLayer::CacheFillThread::run: visible bit done" << std::endl;
 	    }
 
-	    m_layer.m_cachedInitialVisibleArea = true;
-
 	    if (!interrupted && doVisibleFirst) {
 		
 		for (size_t f = visibleEnd; f < end; f += windowIncrement) {
@@ -1356,8 +1470,8 @@
 		    }
 
 
-		    if (++counter == updateAt) {
-			if (f < end) m_fillExtent = f;
+		    if (++counter == updateAt || f == end - 1) {
+			m_fillExtent = f;
 			m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
 							      float(end - start)));
 			counter = 0;
@@ -1386,7 +1500,9 @@
 			break;
 		    }
 		    
-		    if (++counter == updateAt) {
+		    if (++counter == updateAt ||
+			f == visibleEnd - 1 ||
+			f == remainingEnd - 1) {
 			m_fillExtent = f;
 			m_fillCompletion = baseCompletion +
 			    size_t(100 * fabsf(float(f - start) /
@@ -1548,6 +1664,9 @@
 
     bool haveAdj = false;
 
+    bool peaksOnly = (m_binDisplay == PeakBins ||
+		      m_binDisplay == PeakFrequencies);
+
     for (int q = q0i; q <= q1i; ++q) {
 
 	for (int s = s0i; s <= s1i; ++s) {
@@ -1555,20 +1674,19 @@
 	    float binfreq = (sr * q) / m_windowSize;
 	    if (q == q0i) freqMin = binfreq;
 	    if (q == q1i) freqMax = binfreq;
+
+	    if (m_cache->getValueAt(s, q) == NO_VALUE) {
+		continue;
+	    }
 	    
-	    if ((m_frequencyAdjustment == PhaseAdjustedFrequency ||
-		 m_frequencyAdjustment == PhaseAdjustedPeaks) &&
+	    if (m_binDisplay == PeakFrequencies &&
 		m_phaseAdjustCache) {
 
 		unsigned char cadj = m_phaseAdjustCache->getValueAt(s, q);
 		int adjust = int((signed char)cadj);
-		if (adjust == SCHAR_MIN &&
-		    m_frequencyAdjustment == PhaseAdjustedPeaks) {
-		    continue;
-		}
 		
 		float nextBinFreq = (sr * (q + 1)) / m_windowSize;
-		float fadjust = (adjust * (nextBinFreq - binfreq)) / 10.0;//!!!
+		float fadjust = (adjust * (nextBinFreq - binfreq)) / 100.0;//!!!
 		float f = binfreq + fadjust;
 		if (!haveAdj || f < adjFreqMin) adjFreqMin = f;
 		if (!haveAdj || f > adjFreqMax) adjFreqMax = f;
@@ -1599,6 +1717,8 @@
     int s0i = int(s0 + 0.001);
     int s1i = int(s1);
 
+    bool rv = false;
+
     if (m_mutex.tryLock()) {
 	if (m_cache && !m_cacheInvalid) {
 
@@ -1611,25 +1731,24 @@
 		for (int s = s0i; s <= s1i; ++s) {
 		    if (s >= 0 && q >= 0 && s < cw && q < ch) {
 			int value = int(m_cache->getValueAt(s, q));
+			if (value == NO_VALUE) continue;
 			if (min == -1 || value < min) min = value;
 			if (max == -1 || value > max) max = value;
 		    }	
 		}
 	    }
 
-	    if (min < 0) return false;
-
-	    dbMin = (float(min) / 256.0) * 80.0 - 80.0;
-	    dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1;
-
-	    m_mutex.unlock();
-	    return true;
+	    if (min >= 0) {
+		dbMin = (float(min) / 256.0) * 80.0 - 80.0;
+		dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1;
+		rv = true;
+	    }
 	}
 
 	m_mutex.unlock();
     }
 
-    return false;
+    return rv;
 }
    
 void
@@ -1653,16 +1772,7 @@
     std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl;
 #endif
 
-/*
-    if (m_cachedInitialVisibleArea) {
-	if (!m_mutex.tryLock()) {
-	    m_view->update();
-	    return;
-	}
-    } else {
-*/
-	m_mutex.lock();
-//    }
+    m_mutex.lock();
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     std::cerr << "SpectrogramLayer::paint(): locked" << std::endl;
@@ -1794,15 +1904,22 @@
 
     float ymag[h];
     float ydiv[h];
+
+    int sr = m_model->getSampleRate();
     
     size_t bins = m_windowSize / 2;
-    int sr = m_model->getSampleRate();
-    
     if (m_maxFrequency > 0) {
 	bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
 	if (bins > m_windowSize / 2) bins = m_windowSize / 2;
     }
 	
+    size_t minbin = 0;
+    if (m_minFrequency > 0) {
+	minbin = int((double(m_minFrequency) * m_windowSize) / sr + 0.1);
+	if (minbin >= bins) minbin = bins - 1;
+    }
+
+    float minFreq = (float(minbin) * sr) / m_windowSize;
     float maxFreq = (float(bins) * sr) / m_windowSize;
 
     m_mutex.unlock();
@@ -1834,7 +1951,7 @@
 	int s0i = int(s0 + 0.001);
 	int s1i = int(s1);
 
-	for (int q = 0; q < bins; ++q) {
+	for (int q = minbin; q < bins; ++q) {
 
 	    for (int s = s0i; s <= s1i; ++s) {
 
@@ -1845,34 +1962,38 @@
 		float f0 = (float(q) * sr) / m_windowSize;
 		float f1 = (float(q + 1) * sr) / m_windowSize;
  
-		if ((m_frequencyAdjustment == PhaseAdjustedFrequency ||
-		     m_frequencyAdjustment == PhaseAdjustedPeaks) &&
+		if ((m_binDisplay == PeakFrequencies) &&
 		    m_phaseAdjustCache) {
 
 		    unsigned char cadj = m_phaseAdjustCache->getValueAt(s, q);
 		    int adjust = int((signed char)cadj);
-
-		    if (adjust == SCHAR_MIN &&
-			m_frequencyAdjustment == PhaseAdjustedPeaks) {
-			continue;
-		    }
-
-		    float fadjust = (adjust * (f1 - f0)) / 10.0;//!!! was 100
+		    float fadjust = (adjust * (f1 - f0)) / 100.0;
 		    f0 = f1 = f0 + fadjust;
 		}
 	    
-		float y0 = h - (h * f1) / maxFreq;
-		float y1 = h - (h * f0) / maxFreq;
+		float y0 = h - (h * (f1 - minFreq)) / maxFreq;
+		float y1 = h - (h * (f0 - minFreq)) / maxFreq;
+
+		//!!! We want a general View::getYForFrequency and inverse
+		// that can be passed min freq, max freq and log/linear.  We
+		// can then introduce a central correspondence between, say,
+		// spectrogram layer and a frequency-scaled MIDI layer on the
+		// same view.
 
 		if (m_frequencyScale == LogFrequencyScale) {
 		    
-		    float maxf = m_maxFrequency;
-		    if (maxf == 0.0) maxf = float(sr) / 2;
+		    //!!! also, shouldn't be recalculating this every time!
+
+//		    float maxf = m_maxFrequency;
+//		    if (maxf == 0.0) maxf = float(sr) / 2;
 		    
-		    float minf = float(sr) / m_windowSize;
+//		    float minf = float(sr) / m_windowSize;
 		    
-		    float maxlogf = log10f(maxf);
-		    float minlogf = log10f(minf);
+		    float maxlogf = log10f(maxFreq);
+		    float minlogf;
+
+		    if (minFreq > 0) minlogf = log10f(minFreq);
+		    else minlogf = log10f(float(sr) / m_windowSize);
 		    
 		    y0 = h - (h * (log10f(f1) - minlogf)) / (maxlogf - minlogf);
 		    y1 = h - (h * (log10f(f0) - minlogf)) / (maxlogf - minlogf);
@@ -1888,8 +2009,11 @@
 		    float yprop = sprop;
 		    if (y == y0i) yprop *= (y + 1) - y0;
 		    if (y == y1i) yprop *= y1 - y;
-		    
-		    ymag[y] += yprop * m_cache->getValueAt(s, q);
+
+		    int value = m_cache->getValueAt(s, q);
+		    if (value == NO_VALUE) continue;
+
+		    ymag[y] += yprop * value;
 		    ydiv[y] += yprop;
 		}
 	    }
@@ -1915,97 +2039,6 @@
 	m_mutex.unlock();
     }
 
-#ifdef NOT_DEFINED
-    for (int y = 0; y < h; ++y) {
-
-	m_mutex.lock();
-	if (m_cacheInvalid) {
-	    m_mutex.unlock();
-	    break;
-	}
-
-	int cw = m_cache->getWidth();
-	int ch = m_cache->getHeight();
-
-	float q0 = 0, q1 = 0;
-
-	if (!getYBinRange(y0 + y, q0, q1)) {
-	    for (int x = 0; x < w; ++x) {
-		assert(x <= scaled.width());
-		scaled.setPixel(x, y, qRgb(0, 0, 0));
-	    }
-	    m_mutex.unlock();
-	    continue;
-	}
-
-	int q0i = int(q0 + 0.001);
-	int q1i = int(q1);
-
-	for (int x = 0; x < w; ++x) {
-
-	    float s0 = 0, s1 = 0;
-
-	    if (!getXBinRange(x0 + x, s0, s1)) {
-		assert(x <= scaled.width());
-		scaled.setPixel(x, y, qRgb(0, 0, 0));
-		continue;
-	    }
-
-	    int s0i = int(s0 + 0.001);
-	    int s1i = int(s1);
-
-	    float total = 0, divisor = 0;
-
-	    for (int s = s0i; s <= s1i; ++s) {
-
-		float sprop = 1.0;
-		if (s == s0i) sprop *= (s + 1) - s0;
-		if (s == s1i) sprop *= s1 - s;
-
-		for (int q = q0i; q <= q1i; ++q) {
-
-		    float qprop = sprop;
-		    if (q == q0i) qprop *= (q + 1) - q0;
-		    if (q == q1i) qprop *= q1 - q;
-
-		    if (s >= 0 && q >= 0 && s < cw && q < ch) {
-			total += qprop * m_cache->getValueAt(s, q);
-			divisor += qprop;
-		    }
-		}
-	    }
-		    
-	    if (divisor > 0.0) {
-		int pixel = int(total / divisor);
-		if (pixel > 255) pixel = 255;
-		if (pixel < 1) pixel = 1;
-		assert(x <= scaled.width());
-		QColor c = m_cache->getColour(pixel);
-		scaled.setPixel(x, y,
-				qRgb(c.red(), c.green(), c.blue()));
-/*
-		float pixel = total / divisor;
-		float lq = pixel - int(pixel);
-		float hq = int(pixel) + 1 - pixel;
-		int pixNum = int(pixel);
-		QRgb low = m_cache->color(pixNum > 255 ? 255 : pixNum);
-		QRgb high = m_cache->color(pixNum > 254 ? 255 : pixNum + 1);
-		QRgb mixed = qRgb
-		    (qRed(low) * lq + qRed(high) * hq + 0.01,
-		     qGreen(low) * lq + qGreen(high) * hq + 0.01,
-		     qBlue(low) * lq + qBlue(high) * hq + 0.01);
-		scaled.setPixel(x, y, mixed);
-*/
-	    } else {
-		assert(x <= scaled.width());
-		scaled.setPixel(x, y, qRgb(0, 0, 0));
-	    }
-	}
-
-	m_mutex.unlock();
-    }
-#endif
-
     paint.drawImage(x0, y0, scaled);
 
     if (recreateWholePixmapCache) {
@@ -2078,8 +2111,7 @@
 
     QString adjFreqText = "", adjPitchText = "";
 
-    if ((m_frequencyAdjustment == PhaseAdjustedFrequency ||
-	 m_frequencyAdjustment == PhaseAdjustedPeaks) &&
+    if ((m_binDisplay == PeakFrequencies) &&
 	m_phaseAdjustCache) {
 
 	if (!getAdjustedYBinSourceRange(x, y, freqMin, freqMax,
@@ -2225,24 +2257,30 @@
 		 "windowSize=\"%2\" "
 		 "windowType=\"%3\" "
 		 "windowOverlap=\"%4\" "
-		 "gain=\"%5\" ")
+		 "gain=\"%5\" "
+		 "threshold=\"%6\" ")
 	.arg(m_channel)
 	.arg(m_windowSize)
 	.arg(m_windowType)
 	.arg(m_windowOverlap)
-	.arg(m_gain);
-
-    s += QString("maxFrequency=\"%1\" "
-		 "colourScale=\"%2\" "
-		 "colourScheme=\"%3\" "
-		 "frequencyScale=\"%4\" "
-		 "frequencyAdjustment=\"%5\" "
-		 "normalizeColumns=\"%6\"")
+	.arg(m_gain)
+	.arg(m_threshold);
+
+    s += QString("minFrequency=\"%1\" "
+		 "maxFrequency=\"%2\" "
+		 "colourScale=\"%3\" "
+		 "colourScheme=\"%4\" "
+		 "colourRotation=\"%5\" "
+		 "frequencyScale=\"%6\" "
+		 "binDisplay=\"%7\" "
+		 "normalizeColumns=\"%8\"")
+	.arg(m_minFrequency)
 	.arg(m_maxFrequency)
 	.arg(m_colourScale)
 	.arg(m_colourScheme)
+	.arg(m_colourRotation)
 	.arg(m_frequencyScale)
-	.arg(m_frequencyAdjustment)
+	.arg(m_binDisplay)
 	.arg(m_normalizeColumns ? "true" : "false");
 
     return Layer::toXmlString(indent, extraAttributes + " " + s);
@@ -2269,6 +2307,12 @@
     float gain = attributes.value("gain").toFloat(&ok);
     if (ok) setGain(gain);
 
+    float threshold = attributes.value("threshold").toFloat(&ok);
+    if (ok) setThreshold(threshold);
+
+    size_t minFrequency = attributes.value("minFrequency").toUInt(&ok);
+    if (ok) setMinFrequency(minFrequency);
+
     size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
     if (ok) setMaxFrequency(maxFrequency);
 
@@ -2280,13 +2324,16 @@
 	attributes.value("colourScheme").toInt(&ok);
     if (ok) setColourScheme(colourScheme);
 
+    int colourRotation = attributes.value("colourRotation").toInt(&ok);
+    if (ok) setColourRotation(colourRotation);
+
     FrequencyScale frequencyScale = (FrequencyScale)
 	attributes.value("frequencyScale").toInt(&ok);
     if (ok) setFrequencyScale(frequencyScale);
 
-    FrequencyAdjustment frequencyAdjustment = (FrequencyAdjustment)
-	attributes.value("frequencyAdjustment").toInt(&ok);
-    if (ok) setFrequencyAdjustment(frequencyAdjustment);
+    BinDisplay binDisplay = (BinDisplay)
+	attributes.value("binDisplay").toInt(&ok);
+    if (ok) setBinDisplay(binDisplay);
 
     bool normalizeColumns =
 	(attributes.value("normalizeColumns").trimmed() == "true");
--- a/layer/SpectrogramLayer.h	Mon Feb 20 17:23:40 2006 +0000
+++ b/layer/SpectrogramLayer.h	Wed Feb 22 17:45:18 2006 +0000
@@ -39,7 +39,7 @@
     Q_OBJECT
 
 public:
-    enum Configuration { FullRangeDb, MelodicRange };
+    enum Configuration { FullRangeDb, MelodicRange, MelodicPeaks };
     
     SpectrogramLayer(View *w, Configuration = FullRangeDb);
     ~SpectrogramLayer();
@@ -94,11 +94,27 @@
     void setGain(float gain);
     float getGain() const;
 
+    /**
+     * Set the threshold for sample values to be shown in the FFT,
+     * in voltage units.
+     *
+     * The default is 0.0.
+     */
+    void setThreshold(float threshold);
+    float getThreshold() const;
+
+    void setMinFrequency(size_t);
+    size_t getMinFrequency() const;
+
     void setMaxFrequency(size_t); // 0 -> no maximum
     size_t getMaxFrequency() const;
 
-    enum ColourScale { LinearColourScale, MeterColourScale, dBColourScale,
-		       PhaseColourScale };
+    enum ColourScale {
+	LinearColourScale,
+	MeterColourScale,
+	dBColourScale,
+	PhaseColourScale
+    };
 
     /**
      * Specify the scale for sample levels.  See WaveformLayer for
@@ -118,17 +134,17 @@
     void setFrequencyScale(FrequencyScale);
     FrequencyScale getFrequencyScale() const;
 
-    enum FrequencyAdjustment {
-	RawFrequency,
-	PhaseAdjustedFrequency,
-	PhaseAdjustedPeaks
+    enum BinDisplay {
+	AllBins,
+	PeakBins,
+	PeakFrequencies
     };
     
     /**
      * Specify the processing of frequency bins for the y axis.
      */
-    void setFrequencyAdjustment(FrequencyAdjustment);
-    FrequencyAdjustment getFrequencyAdjustment() const;
+    void setBinDisplay(BinDisplay);
+    BinDisplay getBinDisplay() const;
 
     void setNormalizeColumns(bool n);
     bool getNormalizeColumns() const;
@@ -174,14 +190,18 @@
     WindowType          m_windowType;
     size_t              m_windowOverlap;
     float               m_gain;
+    float               m_threshold;
     int                 m_colourRotation;
+    size_t              m_minFrequency;
     size_t              m_maxFrequency;
     ColourScale         m_colourScale;
     ColourScheme        m_colourScheme;
     FrequencyScale      m_frequencyScale;
-    FrequencyAdjustment m_frequencyAdjustment;
+    BinDisplay          m_binDisplay;
     bool                m_normalizeColumns;
 
+    enum { NO_VALUE = 0 };
+    
     // A QImage would do just as well here, and we originally used
     // one: the problem is that we want to munlock() the memory it
     // uses, and it's much easier to do that if we control it.  This
--- a/widgets/Pane.cpp	Mon Feb 20 17:23:40 2006 +0000
+++ b/widgets/Pane.cpp	Wed Feb 22 17:45:18 2006 +0000
@@ -550,7 +550,7 @@
 	    layer->snapToFeatureFrame(snapFrameRight, resolution, Layer::SnapRight);
 	}
 	
-	std::cerr << "snap: frame = " << mouseFrame << ", start frame = " << m_selectionStartFrame << ", left = " << snapFrameLeft << ", right = " << snapFrameRight << std::endl;
+//	std::cerr << "snap: frame = " << mouseFrame << ", start frame = " << m_selectionStartFrame << ", left = " << snapFrameLeft << ", right = " << snapFrameRight << std::endl;
 
 	if (snapFrameLeft < 0) snapFrameLeft = 0;
 	if (snapFrameRight < 0) snapFrameRight = 0;