changeset 976:f2c63ec85901 alignment-simple

Branch to test simple FFT model code
author Chris Cannam
date Mon, 15 Jun 2015 09:15:55 +0100
parents 36cddc3de023 (current diff) b8187c83b93a (diff)
children f535f6e5dbb0
files view/AlignmentView.cpp view/AlignmentView.h view/Pane.cpp view/ViewManager.cpp
diffstat 20 files changed, 340 insertions(+), 146 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -54,6 +54,7 @@
     m_normalizeVisibleArea(false),
     m_normalizeHybrid(false),
     m_invertVertical(false),
+    m_rectified(false),
     m_opaque(false),
     m_smooth(false),
     m_peakResolution(256),
@@ -162,6 +163,7 @@
     list.push_back("Gain");
     list.push_back("Bin Scale");
     list.push_back("Invert Vertical Scale");
+    list.push_back("Show Rectified");
     list.push_back("Opaque");
     list.push_back("Smooth");
     return list;
@@ -175,6 +177,7 @@
     if (name == "Normalize Columns") return tr("Normalize Columns");
     if (name == "Normalize Visible Area") return tr("Normalize Visible Area");
     if (name == "Invert Vertical Scale") return tr("Invert Vertical Scale");
+    if (name == "Show Rectified") return tr("Half-Wave Rectify");
     if (name == "Gain") return tr("Gain");
     if (name == "Opaque") return tr("Always Opaque");
     if (name == "Smooth") return tr("Smooth");
@@ -188,6 +191,7 @@
     if (name == "Normalize Columns") return "normalise-columns";
     if (name == "Normalize Visible Area") return "normalise";
     if (name == "Invert Vertical Scale") return "invert-vertical";
+    if (name == "Show Rectified") return "derivative";
     if (name == "Opaque") return "opaque";
     if (name == "Smooth") return "smooth";
     return "";
@@ -200,6 +204,7 @@
     if (name == "Normalize Columns") return ToggleProperty;
     if (name == "Normalize Visible Area") return ToggleProperty;
     if (name == "Invert Vertical Scale") return ToggleProperty;
+    if (name == "Show Rectified") return ToggleProperty;
     if (name == "Opaque") return ToggleProperty;
     if (name == "Smooth") return ToggleProperty;
     return ValueProperty;
@@ -211,6 +216,7 @@
     if (name == "Normalize Columns" ||
         name == "Normalize Visible Area" ||
 	name == "Colour Scale" ||
+        name == "Show Rectified" ||
         name == "Gain") return tr("Scale");
     if (name == "Bin Scale" ||
         name == "Invert Vertical Scale") return tr("Bins");
@@ -275,6 +281,13 @@
         *deflt = 0;
 	val = (m_invertVertical ? 1 : 0);
 
+    } else if (name == "Show Rectified") {
+
+        if (min) *min = 0;
+        if (max) *max = 0;
+        if (deflt) *deflt = 0;
+        val = (m_rectified ? 1.0 : 0.0);
+
     } else if (name == "Bin Scale") {
 
 	*min = 0;
@@ -355,6 +368,8 @@
 	setNormalizeVisibleArea(value ? true : false);
     } else if (name == "Invert Vertical Scale") {
 	setInvertVertical(value ? true : false);
+    } else if (name == "Show Rectified") {
+        setShowRectified(value > 0.5);
     } else if (name == "Opaque") {
 	setOpaque(value ? true : false);
     } else if (name == "Smooth") {
@@ -472,6 +487,15 @@
 }
 
 void
+Colour3DPlotLayer::setShowRectified(bool show)
+{
+    if (m_rectified == show) return;
+    m_rectified = show;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+void
 Colour3DPlotLayer::setOpaque(bool n)
 {
     if (m_opaque == n) return;
@@ -930,7 +954,20 @@
 {
     Profiler profiler("Colour3DPlotLayer::getColumn");
 
+    DenseThreeDimensionalModel::Column prev;
+    if (m_rectified && (col > m_model->getStartFrame())) {
+        prev = m_model->getColumn(col - 1);
+    }
+    
     DenseThreeDimensionalModel::Column values = m_model->getColumn(col);
+
+    if (m_rectified && !prev.empty()) {
+        for (int y = 0; y < values.size(); ++y) {
+            if (values[y] < prev[y]) values[y] = 0;
+            else values[y] -= prev[y];
+        }
+    }
+    
     while (values.size() < m_model->getHeight()) values.push_back(0.f);
     if (!m_normalizeColumns && !m_normalizeHybrid) return values;
 
--- a/layer/Colour3DPlotLayer.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/Colour3DPlotLayer.h	Mon Jun 15 09:15:55 2015 +0100
@@ -139,6 +139,9 @@
     void setInvertVertical(bool i);
     bool getInvertVertical() const;
 
+    void setShowRectified(bool);
+    bool getShowRectified() const { return m_rectified; }
+
     void setOpaque(bool i);
     bool getOpaque() const;
 
@@ -187,6 +190,7 @@
     bool        m_normalizeVisibleArea;
     bool        m_normalizeHybrid;
     bool        m_invertVertical;
+    bool        m_rectified;
     bool        m_opaque;
     bool        m_smooth;
     int         m_peakResolution;
--- a/layer/Layer.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/Layer.h	Mon Jun 15 09:15:55 2015 +0100
@@ -62,7 +62,7 @@
     Model *getModel() {
 	return const_cast<Model *>(const_cast<const Layer *>(this)->getModel());
     }
-
+    
     /**
      * Return a zoom constraint object defining the supported zoom
      * levels for this layer.  If this returns zero, the layer will
@@ -518,6 +518,13 @@
      */
     virtual RangeMapper *getNewVerticalZoomRangeMapper() const { return 0; }
 
+    /**
+     * Return true if this layer type can function without a model
+     * being set. If false (the default), the layer will not be loaded
+     * from a session if its model cannot be found.
+     */
+    virtual bool canExistWithoutModel() const { return false; }
+
 public slots:
     void showLayer(View *, bool show);
 
--- a/layer/LayerFactory.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/LayerFactory.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -198,7 +198,10 @@
     LayerTypeSet types;
     types.insert(TimeInstants);
     types.insert(TimeValues);
-    types.insert(FlexiNotes);
+    // Because this is strictly a UI function -- list the layer types
+    // to show in a menu -- it should not contain FlexiNotes; the
+    // layer isn't meaningfully editable in SV
+//    types.insert(FlexiNotes);
     types.insert(Notes);
     types.insert(Regions);
     types.insert(Text);
--- a/layer/LayerFactory.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/LayerFactory.h	Mon Jun 15 09:15:55 2015 +0100
@@ -57,6 +57,11 @@
 
     typedef std::set<LayerType> LayerTypeSet;
     LayerTypeSet getValidLayerTypes(Model *model);
+
+    /**
+     * Return the set of layer types that an end user should be
+     * allowed to create, empty, for subsequent editing.
+     */
     LayerTypeSet getValidEmptyLayerTypes();
 
     LayerType getLayerType(const Layer *);
--- a/layer/SpectrogramLayer.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/SpectrogramLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -1629,10 +1629,7 @@
                                        m_windowType,
                                        m_windowSize,
                                        getWindowIncrement(),
-                                       fftSize,
-                                       true, // polar
-                                       StorageAdviser::SpeedCritical,
-                                       m_candidateFillStartFrame);
+                                       fftSize);
 
         if (!model->isOK()) {
             QMessageBox::critical
@@ -1654,8 +1651,6 @@
 
         m_fftModels[v] = FFTFillPair(model, 0);
 
-        model->resume();
-        
         delete m_updateTimer;
         m_updateTimer = new QTimer((SpectrogramLayer *)this);
         connect(m_updateTimer, SIGNAL(timeout()),
@@ -2445,8 +2440,6 @@
         (void)gettimeofday(&tv, 0);
         m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart;
     }
-
-//!!!    if (fftSuspended) fft->resume();
 }
 
 bool
--- a/layer/SpectrumLayer.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/SpectrumLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -112,11 +112,7 @@
                                     m_windowType,
                                     m_windowSize,
                                     getWindowIncrement(),
-                                    m_windowSize,
-                                    false,
-                                    StorageAdviser::Criteria
-                                    (StorageAdviser::SpeedCritical |
-                                     StorageAdviser::FrequentLookupLikely));
+                                    m_windowSize);
 
     setSliceableModel(newFFT);
 
@@ -125,8 +121,6 @@
         m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
     }
 
-    newFFT->resume();
-
     m_newFFTNeeded = false;
 }
 
--- a/layer/TimeRulerLayer.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/TimeRulerLayer.h	Mon Jun 15 09:15:55 2015 +0100
@@ -60,6 +60,8 @@
 
     void setProperties(const QXmlAttributes &attributes);
 
+    virtual bool canExistWithoutModel() const { return true; }
+
 protected:
     Model *m_model;
     LabelHeight m_labelHeight;
--- a/layer/TimeValueLayer.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/TimeValueLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -20,6 +20,7 @@
 #include "base/Profiler.h"
 #include "base/LogRange.h"
 #include "base/RangeMapper.h"
+#include "base/Pitch.h"
 #include "ColourDatabase.h"
 #include "view/View.h"
 
@@ -606,20 +607,31 @@
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
     
+    QString valueText;
+    float value = points.begin()->value;
+    QString unit = getScaleUnits();
+
+    if (unit == "Hz") {
+        valueText = tr("%1 Hz (%2, %3)")
+            .arg(value)
+            .arg(Pitch::getPitchLabelForFrequency(value))
+            .arg(Pitch::getPitchForFrequency(value));
+    } else if (unit != "") {
+        valueText = tr("%1 %2").arg(value).arg(unit);
+    } else {
+        valueText = tr("%1").arg(value);
+    }
+    
     QString text;
-    QString unit = getScaleUnits();
-    if (unit != "") unit = " " + unit;
 
     if (points.begin()->label == "") {
-	text = QString(tr("Time:\t%1\nValue:\t%2%3\nNo label"))
+	text = QString(tr("Time:\t%1\nValue:\t%2\nNo label"))
 	    .arg(rt.toText(true).c_str())
-	    .arg(points.begin()->value)
-            .arg(unit);
+	    .arg(valueText);
     } else {
-	text = QString(tr("Time:\t%1\nValue:\t%2%3\nLabel:\t%4"))
+	text = QString(tr("Time:\t%1\nValue:\t%2\nLabel:\t%4"))
 	    .arg(rt.toText(true).c_str())
-	    .arg(points.begin()->value)
-            .arg(unit)
+	    .arg(valueText)
 	    .arg(points.begin()->label);
     }
 
--- a/layer/WaveformLayer.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/layer/WaveformLayer.h	Mon Jun 15 09:15:55 2015 +0100
@@ -202,6 +202,8 @@
     virtual int getCurrentVerticalZoomStep() const;
     virtual void setVerticalZoomStep(int);
 
+    virtual bool canExistWithoutModel() const { return true; }
+
 protected:
     int dBscale(double sample, int m) const;
 
--- a/view/AlignmentView.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/view/AlignmentView.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -32,14 +32,14 @@
 }
 
 void
-AlignmentView::globalCentreFrameChanged(int f)
+AlignmentView::globalCentreFrameChanged(sv_frame_t f)
 {
     View::globalCentreFrameChanged(f);
     update();
 }
 
 void
-AlignmentView::viewCentreFrameChanged(View *v, int f)
+AlignmentView::viewCentreFrameChanged(View *v, sv_frame_t f)
 {
     View::viewCentreFrameChanged(v, f);
     if (v == m_above) {
@@ -51,7 +51,7 @@
 }
 
 void
-AlignmentView::viewManagerPlaybackFrameChanged(int)
+AlignmentView::viewManagerPlaybackFrameChanged(sv_frame_t)
 {
     update();
 }
@@ -127,12 +127,12 @@
 
     paint.fillRect(rect(), bg);
 
-    vector<int> keyFrames = getKeyFrames();
+    vector<sv_frame_t> keyFrames = getKeyFrames();
 
-    foreach (int f, keyFrames) {
+    foreach (sv_frame_t f, keyFrames) {
 	int ax = m_above->getXForFrame(f);
-	int rf = m_above->alignToReference(f);
-	int bf = m_below->alignFromReference(rf);
+	sv_frame_t rf = m_above->alignToReference(f);
+	sv_frame_t bf = m_below->alignFromReference(rf);
 	int bx = m_below->getXForFrame(bf);
 	paint.drawLine(ax, 0, bx, height());
     }
@@ -140,7 +140,7 @@
     paint.end();
 }
 
-vector<int>
+vector<sv_frame_t>
 AlignmentView::getKeyFrames()
 {
     if (!m_above) {
@@ -163,7 +163,7 @@
 	return getDefaultKeyFrames();
     }
 
-    vector<int> keyFrames;
+    vector<sv_frame_t> keyFrames;
 
     const SparseOneDimensionalModel::PointList pp = m->getPoints();
     for (SparseOneDimensionalModel::PointList::const_iterator pi = pp.begin();
@@ -174,19 +174,19 @@
     return keyFrames;
 }
 
-vector<int>
+vector<sv_frame_t>
 AlignmentView::getDefaultKeyFrames()
 {
-    vector<int> keyFrames;
+    vector<sv_frame_t> keyFrames;
 
     if (!m_above || !m_manager) return keyFrames;
 
-    int rate = m_manager->getMainModelSampleRate();
+    sv_samplerate_t rate = m_manager->getMainModelSampleRate();
     if (rate == 0) return keyFrames;
 
-    for (int f = m_above->getModelsStartFrame(); 
+    for (sv_frame_t f = m_above->getModelsStartFrame(); 
 	 f <= m_above->getModelsEndFrame(); 
-	 f += rate * 5) {
+	 f += sv_frame_t(rate * 5 + 0.5)) {
 	keyFrames.push_back(f);
     }
     
--- a/view/AlignmentView.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/view/AlignmentView.h	Mon Jun 15 09:15:55 2015 +0100
@@ -30,18 +30,18 @@
     void setViewBelow(View *view);
 
 public slots:
-    virtual void globalCentreFrameChanged(int);
-    virtual void viewCentreFrameChanged(View *, int);
+    virtual void globalCentreFrameChanged(sv_frame_t);
+    virtual void viewCentreFrameChanged(View *, sv_frame_t);
     virtual void viewAboveZoomLevelChanged(int, bool);
     virtual void viewBelowZoomLevelChanged(int, bool);
-    virtual void viewManagerPlaybackFrameChanged(int);
+    virtual void viewManagerPlaybackFrameChanged(sv_frame_t);
 
 protected:
     virtual void paintEvent(QPaintEvent *e);
     virtual bool shouldLabelSelections() const { return false; }
 
-    std::vector<int> getKeyFrames();
-    std::vector<int> getDefaultKeyFrames();
+    std::vector<sv_frame_t> getKeyFrames();
+    std::vector<sv_frame_t> getDefaultKeyFrames();
 
     View *m_above;
     View *m_below;
--- a/view/Overview.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/view/Overview.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -35,6 +35,10 @@
     m_followZoom = false;
     setPlaybackFollow(PlaybackIgnore);
     m_modelTestTime.start();
+
+    bool light = hasLightBackground();
+    if (light) m_boxColour = Qt::darkGray;
+    else m_boxColour = Qt::lightGray;
 }
 
 void
@@ -159,6 +163,12 @@
 }
 
 void
+Overview::setBoxColour(QColor c)
+{
+    m_boxColour = c;
+}
+
+void
 Overview::paintEvent(QPaintEvent *e)
 {
     // Recalculate zoom in case the size of the widget has changed.
@@ -263,7 +273,7 @@
     
     foreach (QRect vr, rects) {
         paint.setBrush(Qt::NoBrush);
-        paint.setPen(QPen(Qt::gray, 2));
+        paint.setPen(QPen(m_boxColour, 2));
         paint.drawRoundedRect(vr, 4, 4);
     }
 
--- a/view/Overview.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/view/Overview.h	Mon Jun 15 09:15:55 2015 +0100
@@ -49,6 +49,8 @@
     virtual void viewZoomLevelChanged(View *, int, bool);
     virtual void viewManagerPlaybackFrameChanged(sv_frame_t);
 
+    virtual void setBoxColour(QColor);
+    
 protected:
     virtual void paintEvent(QPaintEvent *e);
     virtual void mousePressEvent(QMouseEvent *e);
@@ -67,6 +69,7 @@
     bool m_clickedInRange;
     sv_frame_t m_dragCentreFrame;
     QTime m_modelTestTime;
+    QColor m_boxColour;
     
     typedef std::set<View *> ViewSet;
     ViewSet m_views;
--- a/view/Pane.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/view/Pane.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -388,10 +388,10 @@
 Pane::selectionIsBeingEdited() const
 {
     if (!m_editingSelection.isEmpty()) {
-    if (m_mousePos != m_clickPos &&
-        getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
-        return true;
-    }
+        if (m_mousePos != m_clickPos &&
+            getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
+            return true;
+        }
     }
     return false;
 }
@@ -2115,14 +2115,25 @@
         max = snapFrameRight;
     }
 
+    sv_frame_t end = getModelsEndFrame();
+    if (min > end) min = end;
+    if (max > end) max = end;
+
     if (m_manager) {
-        m_manager->setInProgressSelection(Selection(alignToReference(min),
-                                                    alignToReference(max)),
-                                          !m_resizing && !m_ctrlPressed);
+
+        Selection sel(alignToReference(min), alignToReference(max));
+
+        bool exc;
+        bool same = (m_manager->haveInProgressSelection() &&
+                     m_manager->getInProgressSelection(exc) == sel);
+        
+        m_manager->setInProgressSelection(sel, !m_resizing && !m_ctrlPressed);
+
+        if (!same) {
+            edgeScrollMaybe(e->x());
+        }
     }
 
-    edgeScrollMaybe(e->x());
-
     update();
 
     if (min != max) {
@@ -2145,11 +2156,12 @@
         sv_frame_t offset = mouseFrame - getStartFrame();
         sv_frame_t available = getEndFrame() - getStartFrame();
         sv_frame_t move = 0;
-        if (offset >= double(available) * 0.95) {
-            move = sv_frame_t(double(offset - available) * 0.95) + 1;
-        } else if (offset <= double(available) * 0.10) {
-            move = sv_frame_t(double(available) * 0.10 - double(offset)) + 1;
-            move = -move;
+        sv_frame_t rightEdge = available - (available / 20);
+        sv_frame_t leftEdge = (available / 10);
+        if (offset >= rightEdge) {
+            move = offset - rightEdge + 1;
+        } else if (offset <= leftEdge) {
+            move = offset - leftEdge - 1;
         }
         if (move != 0) {
             setCentreFrame(m_centreFrame + move);
--- a/view/ViewManager.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/view/ViewManager.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -166,6 +166,7 @@
 void
 ViewManager::setPlaybackFrame(sv_frame_t f)
 {
+    if (f < 0) f = 0;
     if (m_playbackFrame != f) {
 	m_playbackFrame = f;
 	emit playbackFrameChanged(f);
--- a/widgets/CSVFormatDialog.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/widgets/CSVFormatDialog.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -121,16 +121,37 @@
     layout->addWidget(new QLabel(tr("Timing is specified:")), row, 0);
     
     m_timingTypeCombo = new QComboBox;
-    m_timingTypeCombo->addItem(tr("Explicitly, in seconds"));
-    m_timingTypeCombo->addItem(tr("Explicitly, in milliseconds"));
-    m_timingTypeCombo->addItem(tr("Explicitly, in audio sample frames"));
-    m_timingTypeCombo->addItem(tr("Implicitly: rows are equally spaced in time"));
+
+    m_timingLabels = {
+        { TimingExplicitSeconds, tr("Explicitly, in seconds") },
+        { TimingExplicitMsec, tr("Explicitly, in milliseconds") },
+        { TimingExplicitSamples, tr("Explicitly, in audio sample frames") },
+        { TimingImplicit, tr("Implicitly: rows are equally spaced in time") }
+    };
+
+    for (auto &l: m_timingLabels) {
+        m_timingTypeCombo->addItem(l.second);
+    }
+
     layout->addWidget(m_timingTypeCombo, row++, 1, 1, 2);
+
     connect(m_timingTypeCombo, SIGNAL(activated(int)),
 	    this, SLOT(timingTypeChanged(int)));
-    m_timingTypeCombo->setCurrentIndex
-        (m_format.getTimingType() == CSVFormat::ExplicitTiming ?
-         m_format.getTimeUnits() == CSVFormat::TimeSeconds ? 0 : 2 : 3);
+
+    m_initialTimingOption = TimingImplicit;
+    if (m_format.getTimingType() == CSVFormat::ExplicitTiming) {
+        switch (m_format.getTimeUnits()) {
+        case CSVFormat::TimeSeconds:
+            m_initialTimingOption = TimingExplicitSeconds; break;
+        case CSVFormat::TimeMilliseconds:
+            m_initialTimingOption = TimingExplicitMsec; break;
+        case CSVFormat::TimeAudioFrames:
+            m_initialTimingOption = TimingExplicitSamples; break;
+        case CSVFormat::TimeWindows:
+            m_initialTimingOption = TimingImplicit; break;
+        }
+    }
+    m_timingTypeCombo->setCurrentIndex(int(m_initialTimingOption));
 
     m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):"));
     layout->addWidget(m_sampleRateLabel, row, 0);
@@ -189,7 +210,6 @@
     setLayout(layout);
 
     timingTypeChanged(m_timingTypeCombo->currentIndex());
-    updateModelLabel();
 }
 
 CSVFormatDialog::~CSVFormatDialog()
@@ -230,46 +250,66 @@
 }
 
 void
+CSVFormatDialog::applyStartTimePurpose()
+{
+    // First check if we already have any. NB there may be fewer than
+    // m_format.getColumnCount() elements in m_columnPurposeCombos
+    // (because of the fuzzy column behaviour)
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        QComboBox *cb = m_columnPurposeCombos[i];
+        if (cb->currentIndex() == int(CSVFormat::ColumnStartTime)) {
+            return;
+        }
+    }
+    // and if not, select one
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        QComboBox *cb = m_columnPurposeCombos[i];
+        if (cb->currentIndex() == int(CSVFormat::ColumnValue)) {
+            cb->setCurrentIndex(int(CSVFormat::ColumnStartTime));
+            return;
+        }
+    }
+}
+
+void
+CSVFormatDialog::removeStartTimePurpose()
+{
+    // NB there may be fewer than m_format.getColumnCount() elements
+    // in m_columnPurposeCombos (because of the fuzzy column
+    // behaviour)
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        QComboBox *cb = m_columnPurposeCombos[i];
+        if (cb->currentIndex() == int(CSVFormat::ColumnStartTime)) {
+            cb->setCurrentIndex(int(CSVFormat::ColumnValue));
+        }
+    }
+}
+
+void
+CSVFormatDialog::updateComboVisibility()
+{
+    bool wantRate = (m_format.getTimingType() == CSVFormat::ImplicitTiming ||
+                     m_format.getTimeUnits() == CSVFormat::TimeAudioFrames);
+    bool wantWindow = (m_format.getTimingType() == CSVFormat::ImplicitTiming);
+    
+    m_sampleRateCombo->setEnabled(wantRate);
+    m_sampleRateLabel->setEnabled(wantRate);
+
+    m_windowSizeCombo->setEnabled(wantWindow);
+    m_windowSizeLabel->setEnabled(wantWindow);
+}
+
+void
 CSVFormatDialog::timingTypeChanged(int type)
 {
-    switch (type) {
-
-    case 0:
-	m_format.setTimingType(CSVFormat::ExplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeSeconds);
-	m_sampleRateCombo->setEnabled(false);
-	m_sampleRateLabel->setEnabled(false);
-	m_windowSizeCombo->setEnabled(false);
-	m_windowSizeLabel->setEnabled(false);
-	break;
-
-    case 1:
-	m_format.setTimingType(CSVFormat::ExplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeMilliseconds);
-	m_sampleRateCombo->setEnabled(true);
-	m_sampleRateLabel->setEnabled(true);
-	m_windowSizeCombo->setEnabled(false);
-	m_windowSizeLabel->setEnabled(false);
-	break;
-
-    case 2:
-	m_format.setTimingType(CSVFormat::ExplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeAudioFrames);
-	m_sampleRateCombo->setEnabled(true);
-	m_sampleRateLabel->setEnabled(true);
-	m_windowSizeCombo->setEnabled(false);
-	m_windowSizeLabel->setEnabled(false);
-	break;
-
-    case 3:
-	m_format.setTimingType(CSVFormat::ImplicitTiming);
-	m_format.setTimeUnits(CSVFormat::TimeWindows);
-	m_sampleRateCombo->setEnabled(true);
-	m_sampleRateLabel->setEnabled(true);
-	m_windowSizeCombo->setEnabled(true);
-	m_windowSizeLabel->setEnabled(true);
-	break;
+    // Update any column purpose combos
+    if (TimingOption(type) == TimingImplicit) {
+        removeStartTimePurpose();
+    } else {
+        applyStartTimePurpose();
     }
+    updateFormatFromDialog();
+    updateComboVisibility();
 }
 
 void
@@ -292,43 +332,26 @@
 CSVFormatDialog::columnPurposeChanged(int p)
 {
     QObject *o = sender();
-
     QComboBox *cb = qobject_cast<QComboBox *>(o);
     if (!cb) return;
 
     CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)p;
 
-    bool haveStartTime = false;
-    bool haveDuration = false;
-    bool havePitch = false;
-    int valueCount = 0;
-
+    bool haveStartTime = false; // so as to update timing type combo appropriately
+    
+    // Ensure the column purpose combos are consistent with one
+    // another, without reference to m_format (which we'll update
+    // separately)
+    
     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
 
-        CSVFormat::ColumnPurpose cp = m_format.getColumnPurpose(i);
-
-        bool thisChanged = (cb == m_columnPurposeCombos[i]);
+        QComboBox *thisCombo = m_columnPurposeCombos[i];
         
-        if (thisChanged) {
-
-            cerr << "i == " << i << ", fuzzy == " << m_fuzzyColumn
-                      << ", p == " << p << endl;
-
-            if (i == m_fuzzyColumn) {
-                for (int j = i; j < m_format.getColumnCount(); ++j) {
-                    if (p == 0) { // Ignore
-                        m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
-                    } else { // Value
-                        m_format.setColumnPurpose(j, CSVFormat::ColumnValue);
-                        ++valueCount;
-                    }
-                }
-                continue;
-            }
-
-            cp = purpose;
-
-        } else {
+        CSVFormat::ColumnPurpose cp = (CSVFormat::ColumnPurpose)
+            (thisCombo->currentIndex());
+        bool thisChanged = (cb == thisCombo);
+        
+        if (!thisChanged) {
 
             if (i == m_fuzzyColumn) continue;
 
@@ -353,29 +376,98 @@
                     cp = CSVFormat::ColumnUnknown;
                 }
             }
+
+            if (cp == CSVFormat::ColumnStartTime) {
+                haveStartTime = true;
+            }
+        
+            thisCombo->setCurrentIndex(int(cp));
+
+        } else {
+            if (purpose == CSVFormat::ColumnStartTime) {
+                haveStartTime = true;
+            }
         }
+    }
 
-        if (cp == CSVFormat::ColumnStartTime) {
+    if (!haveStartTime) {
+        m_timingTypeCombo->setCurrentIndex(int(TimingImplicit));
+    } else if (m_timingTypeCombo->currentIndex() == int(TimingImplicit)) {
+        if (m_initialTimingOption == TimingImplicit) {
+            m_timingTypeCombo->setCurrentIndex(TimingExplicitSeconds);
+        } else {
+            m_timingTypeCombo->setCurrentIndex(m_initialTimingOption);
+        }
+    }
+
+    updateFormatFromDialog();
+    updateComboVisibility();
+}
+
+void
+CSVFormatDialog::updateFormatFromDialog()
+{
+    switch (TimingOption(m_timingTypeCombo->currentIndex())) {
+
+    case TimingExplicitSeconds:
+	m_format.setTimingType(CSVFormat::ExplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeSeconds);
+	break;
+
+    case TimingExplicitMsec:
+	m_format.setTimingType(CSVFormat::ExplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeMilliseconds);
+	break;
+
+    case TimingExplicitSamples:
+	m_format.setTimingType(CSVFormat::ExplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeAudioFrames);
+	break;
+
+    case TimingImplicit:
+	m_format.setTimingType(CSVFormat::ImplicitTiming);
+	m_format.setTimeUnits(CSVFormat::TimeWindows);
+	break;
+    }
+
+    bool haveStartTime = false;
+    bool haveDuration = false;
+    bool havePitch = false;
+    int valueCount = 0;
+
+    for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+
+        QComboBox *thisCombo = m_columnPurposeCombos[i];
+        
+        CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)
+            (thisCombo->currentIndex());
+
+        if (purpose == CSVFormat::ColumnStartTime) {
             haveStartTime = true;
         }
-        if (cp == CSVFormat::ColumnEndTime ||
-            cp == CSVFormat::ColumnDuration) {
+        if (purpose == CSVFormat::ColumnEndTime ||
+            purpose == CSVFormat::ColumnDuration) {
             haveDuration = true;
         }
-        if (cp == CSVFormat::ColumnPitch) {
+        if (purpose == CSVFormat::ColumnPitch) {
             havePitch = true;
         }
-        if (cp == CSVFormat::ColumnValue) {
+        if (purpose == CSVFormat::ColumnValue) {
             ++valueCount;
         }
 
-        m_columnPurposeCombos[i]->setCurrentIndex(int(cp));
-        m_format.setColumnPurpose(i, cp);
-    }
+        m_format.setColumnPurpose(i, purpose);
 
-    if (!haveStartTime) {
-        m_timingTypeCombo->setCurrentIndex(2);
-        timingTypeChanged(2);
+        if (i == m_fuzzyColumn) {
+            for (int j = i + 1; j < m_format.getColumnCount(); ++j) {
+                if (purpose == CSVFormat::ColumnUnknown) {
+                    m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
+                } else { // Value
+                    m_format.setColumnPurpose(j, CSVFormat::ColumnValue);
+                    ++valueCount;
+                }
+            }
+        }
     }
 
     if (haveStartTime && haveDuration) {
@@ -398,3 +490,4 @@
 }
 
 
+
--- a/widgets/CSVFormatDialog.h	Mon Apr 20 09:19:52 2015 +0100
+++ b/widgets/CSVFormatDialog.h	Mon Jun 15 09:15:55 2015 +0100
@@ -40,11 +40,26 @@
     void sampleRateChanged(QString);
     void windowSizeChanged(QString);
     void columnPurposeChanged(int purpose);
+
+    void updateFormatFromDialog();
     void updateModelLabel();
 
 protected:
     CSVFormat m_format;
     int m_maxDisplayCols;
+
+    enum TimingOption {
+        TimingExplicitSeconds = 0,
+        TimingExplicitMsec,
+        TimingExplicitSamples,
+        TimingImplicit
+    };
+    std::map<TimingOption, QString> m_timingLabels;
+    TimingOption m_initialTimingOption;
+
+    void updateComboVisibility();
+    void applyStartTimePurpose();
+    void removeStartTimePurpose();
     
     QComboBox *m_timingTypeCombo;
     QLabel *m_sampleRateLabel;
--- a/widgets/WindowShapePreview.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/widgets/WindowShapePreview.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -209,7 +209,6 @@
 void
 WindowShapePreview::setWindowType(WindowType type)
 {
-    if (m_windowType == type) return;
     m_windowType = type;
     updateLabels();
 }
--- a/widgets/WindowTypeSelector.cpp	Mon Apr 20 09:19:52 2015 +0100
+++ b/widgets/WindowTypeSelector.cpp	Mon Jun 15 09:15:55 2015 +0100
@@ -78,7 +78,9 @@
 
     connect(m_windowCombo, SIGNAL(currentIndexChanged(int)),
             this, SLOT(windowIndexChanged(int)));
-    windowIndexChanged(index);
+
+    m_windowType = defaultType;
+    m_windowShape->setWindowType(m_windowType);
 }
 
 WindowTypeSelector::~WindowTypeSelector()