changeset 172:d95c4cdef8af

Merge
author Chris Cannam
date Mon, 02 Nov 2015 11:32:30 +0000
parents 7de720f503a5 (diff) c40de221b5df (current diff)
children d22f69c2b025
files CITATION Chordino.cpp Makefile.osx NNLSBase.cpp
diffstat 13 files changed, 337 insertions(+), 178 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Thu Sep 05 17:30:38 2013 +0100
+++ b/.hgtags	Mon Nov 02 11:32:30 2015 +0000
@@ -1,3 +1,8 @@
 12fd1d3ccd6e0072fc4be295e7a1e383e84912a1 0.1
 12fd1d3ccd6e0072fc4be295e7a1e383e84912a1 0.1
 dab7e7bfeba17478fa6c5af050c6270026ad635b 0.2
+2cd99c0810f28abe2fcbf2c50a29fee20b910ae4 mirex2013
+9e9267d6d78a8e67f2a4dc1aac4ea1aab135f4f0 v1.0
+0a743c2dac6aa0eaa093d5291c697e51eac1035e v1.1
+0a743c2dac6aa0eaa093d5291c697e51eac1035e v1.1
+ba9310bcc3fcb3c58c99d25837b6f62662666b7d v1.1
--- a/Chordino.cpp	Thu Sep 05 17:30:38 2013 +0100
+++ b/Chordino.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -121,7 +121,7 @@
     list.push_back(whiteningParam);
 
     ParameterDescriptor spectralShapeParam;
-    spectralShapeParam.identifier = "spectralshape";
+    spectralShapeParam.identifier = "s";
     spectralShapeParam.name = "spectral shape";
     spectralShapeParam.description = "Determines how individual notes in the note dictionary look: higher values mean more dominant higher harmonics.";
     spectralShapeParam.unit = "";
@@ -167,6 +167,9 @@
     
     int index = 0;
 
+    float featureRate =
+        (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    
     OutputDescriptor d7;
     d7.identifier = "simplechord";
     d7.name = "Chord Estimate";
@@ -178,7 +181,7 @@
     d7.isQuantized = false;
     d7.sampleType = OutputDescriptor::VariableSampleRate;
     d7.hasDuration = false;
-    d7.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    d7.sampleRate = featureRate;
     list.push_back(d7);
     m_outputChords = index++;
     
@@ -196,7 +199,7 @@
     chordnotes.quantizeStep = 1;
     chordnotes.sampleType = OutputDescriptor::VariableSampleRate;
     chordnotes.hasDuration = true;
-    chordnotes.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    chordnotes.sampleRate = featureRate;
     list.push_back(chordnotes);
     m_outputChordnotes = index++;
     
@@ -210,6 +213,7 @@
     d8.hasKnownExtents = false;
     d8.isQuantized = false;
     d8.sampleType = OutputDescriptor::FixedSampleRate;
+    d8.sampleRate = featureRate;
     d8.hasDuration = false;
     list.push_back(d8);
     m_outputHarmonicChange = index++;
@@ -224,6 +228,7 @@
     loglikelihood.hasKnownExtents = false;
     loglikelihood.isQuantized = false;
     loglikelihood.sampleType = OutputDescriptor::FixedSampleRate;
+    loglikelihood.sampleRate = featureRate;
     loglikelihood.hasDuration = false;
     list.push_back(loglikelihood);
     m_outputLoglikelihood = index++;
@@ -295,7 +300,7 @@
         calculate a tuned log-frequency spectrogram (currentTunedSpec): use the tuning estimated above (kinda f0) to 
         perform linear interpolation on the existing log-frequency spectrogram (kinda currentLogSpectrum).
     **/
-    cerr << endl << "[Chordino Plugin] Tuning Log-Frequency Spectrogram ... ";
+    if (debug_on) cerr << endl << "[Chordino Plugin] Tuning Log-Frequency Spectrogram ... ";
 					
     int count = 0;
 		
@@ -346,7 +351,7 @@
         tunedSpec.push_back(currentTunedSpec);
         count++;
     }
-    cerr << "done." << endl;
+    if (debug_on) cerr << "done." << endl;
 	    
     /** Semitone spectrum and chromagrams
         Semitone-spaced log-frequency spectrum derived from the tuned log-freq spectrum above. the spectrum
@@ -355,9 +360,9 @@
         bass and treble stacked onto each other).
     **/
     if (m_useNNLS == 0) {
-        cerr << "[Chordino Plugin] Mapping to semitone spectrum and chroma ... ";
+        if (debug_on) cerr << "[Chordino Plugin] Mapping to semitone spectrum and chroma ... ";
     } else {
-        cerr << "[Chordino Plugin] Performing NNLS and mapping to chroma ... ";
+        if (debug_on) cerr << "[Chordino Plugin] Performing NNLS and mapping to chroma ... ";
     }
 
 	    
@@ -368,7 +373,7 @@
 
     FeatureList chromaList;
     
-    
+    bool clipwarned = false;
 
     for (FeatureList::iterator it = tunedSpec.begin(); it != tunedSpec.end(); ++it) {
         Feature currentTunedSpec = *it; // logfreq spectrum
@@ -449,7 +454,7 @@
         vector<float> origchroma = chroma;
         chroma.insert(chroma.begin(), basschroma.begin(), basschroma.end()); // just stack the both chromas 
         currentChromas.values = chroma;
- 
+
         if (m_doNormalizeChroma > 0) {
             vector<float> chromanorm = vector<float>(3,0);			
             switch (int(m_doNormalizeChroma)) {
@@ -489,15 +494,22 @@
         for (int iChord = 0; iChord < nChord; iChord++) {
             tempchordvalue = 0;
             for (int iBin = 0; iBin < 12; iBin++) {
-                tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin];                
+                tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin];
             }
             for (int iBin = 12; iBin < 24; iBin++) {
                 tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin];
             }
             if (iChord == nChord-1) tempchordvalue *= .7;
             if (tempchordvalue < 0) tempchordvalue = 0.0;
-            tempchordvalue = pow(1.3,tempchordvalue);
-            sumchordvalue+=tempchordvalue;
+            if (tempchordvalue > 200.0) {
+                if (!clipwarned) {
+                    cerr << "WARNING: interim chroma contains extreme chord value " << tempchordvalue << ", clipping this and any others that appear" << endl;
+                    clipwarned = true;
+                }
+                tempchordvalue = 200.0;
+            }
+            tempchordvalue = pow(1.3, tempchordvalue);
+            sumchordvalue += tempchordvalue;
             currentChordSalience.push_back(tempchordvalue);
         }
         if (sumchordvalue > 0) {
@@ -511,11 +523,11 @@
 	        
         count++;
     }
-    cerr << "done." << endl;
+    if (debug_on) cerr << "done." << endl;
 		
     vector<Feature> oldnotes;
 
-    cerr << "[Chordino Plugin] HMM Chord Estimation ... ";
+    if (debug_on) cerr << "[Chordino Plugin] HMM Chord Estimation ... ";
     int oldchord = nChord-1;
     double selftransprob = 0.99;
  
@@ -534,7 +546,6 @@
     vector<double> scale;
     vector<int> chordpath = ViterbiPath(init, trans, chordogram, delta, &scale);
     
-
     Feature chord_feature; // chord estimate
     chord_feature.hasTimestamp = true;
     chord_feature.timestamp = timestamps[0];
@@ -543,7 +554,6 @@
     
     chordchange[0] = 0;
     for (int iFrame = 1; iFrame < (int)chordpath.size(); ++iFrame) {
-        // cerr << chordpath[iFrame] << endl;
         if (chordpath[iFrame] != oldchord ) {
             // chord
             Feature chord_feature; // chord estimate
@@ -570,7 +580,11 @@
         }
         /* calculating simple chord change prob */            
         for (int iChord = 0; iChord < nChord; iChord++) {
-            chordchange[iFrame-1] += delta[(iFrame-1)*nChord + iChord] * log(delta[(iFrame-1)*nChord + iChord]/delta[iFrame*nChord + iChord]);
+            double num = delta[(iFrame-1) * nChord + iChord];
+            double denom = delta[iFrame * nChord + iChord];
+            double eps = 1e-7;
+            if (denom < eps) denom = eps;
+            chordchange[iFrame-1] += num * log(num / denom + eps);
         }
     }
     
@@ -596,19 +610,20 @@
         fsOut[m_outputChordnotes].push_back(oldnotes[iNote]);
     }
     
-    cerr << "done." << endl;
-    
+    if (debug_on) cerr << "done." << endl;
+
     for (int iFrame = 0; iFrame < nFrame; iFrame++) {
         Feature chordchange_feature;
         chordchange_feature.hasTimestamp = true;
         chordchange_feature.timestamp = timestamps[iFrame];
         chordchange_feature.values.push_back(chordchange[iFrame]);
-        // cerr << chordchange[iFrame] << endl;
+//        cerr << "putting value " << chordchange[iFrame] << " at time " << chordchange_feature.timestamp << endl;
         fsOut[m_outputHarmonicChange].push_back(chordchange_feature);
     }
+
+    free(delta);
     
     // for (int iFrame = 0; iFrame < nFrame; iFrame++) cerr << fsOut[m_outputHarmonicChange][iFrame].values[0] << endl;
     
-    
     return fsOut;     
 }
--- a/Makefile.linux	Thu Sep 05 17:30:38 2013 +0100
+++ b/Makefile.linux	Mon Nov 02 11:32:30 2015 +0000
@@ -1,30 +1,29 @@
-
-PLUGIN_LIBRARY_NAME = nnls-chroma
-
-PLUGIN_CODE_OBJECTS = chromamethods.o NNLSBase.o NNLSChroma.o Chordino.o Tuning.o plugins.o nnls.o viterbi.o
-
-VAMP_SDK_DIR = ../vamp-plugin-sdk
-
-
-ARCHFLAGS = -O3 -ftree-vectorize -ffast-math
-#ARCHFLAGS = -g
-
-CFLAGS = $(ARCHFLAGS) -I$(VAMP_SDK_DIR) -Wall -fPIC
-CXXFLAGS = $(ARCHFLAGS) -I$(VAMP_SDK_DIR) -Wall -fPIC
-PLUGIN_EXT = .so
-PLUGIN = $(PLUGIN_LIBRARY_NAME)$(PLUGIN_EXT)
-LDFLAGS = -shared -Wl,-soname=$(PLUGIN) $(VAMP_SDK_DIR)/libvamp-sdk.a -Wl,--version-script=vamp-plugin.map 
-
-
-$(PLUGIN): $(PLUGIN_CODE_OBJECTS)
-	   $(CXX) -o $@ $^ $(LDFLAGS)
-
-nnls.o:	nnls.c		# not nnls.f
-
-clean:
-	rm -f *.o
-
-# DO NOT DELETE
+
+PLUGIN_LIBRARY_NAME = nnls-chroma
+
+PLUGIN_CODE_OBJECTS = chromamethods.o NNLSBase.o NNLSChroma.o Chordino.o Tuning.o plugins.o nnls.o viterbi.o
+
+VAMP_SDK_DIR = ../vamp-plugin-sdk
+
+ARCHFLAGS = -O3 -ftree-vectorize -ffast-math
+#ARCHFLAGS = -g
+
+CFLAGS += $(ARCHFLAGS) -I$(VAMP_SDK_DIR) -Wall -fPIC
+CXXFLAGS += $(ARCHFLAGS) -I$(VAMP_SDK_DIR) -Wall -fPIC
+PLUGIN_EXT = .so
+PLUGIN = $(PLUGIN_LIBRARY_NAME)$(PLUGIN_EXT)
+LDFLAGS += -shared -Wl,-soname=$(PLUGIN) -L$(VAMP_SDK_DIR) -lvamp-sdk -Wl,--version-script=vamp-plugin.map 
+
+
+$(PLUGIN): $(PLUGIN_CODE_OBJECTS)
+	   $(CXX) -o $@ $^ $(LDFLAGS)
+
+nnls.o:	nnls.c		# not nnls.f
+
+clean:
+	rm -f *.o
+
+# DO NOT DELETE
 
 Chordino.o: Chordino.h NNLSBase.h chromamethods.h nnls.h viterbi.h
 chromamethods.o: chromamethods.h nnls.h
--- a/Makefile.mingw	Thu Sep 05 17:30:38 2013 +0100
+++ b/Makefile.mingw	Mon Nov 02 11:32:30 2015 +0000
@@ -5,23 +5,25 @@
 
 VAMP_SDK_DIR = ../vamp-plugin-sdk
 
-CC=gcc
-CXX=g++
+# Allow the invoker to specify a particular set of tools through
+# TOOLPREFIX, e.g. for cross-compile
+CC=$(TOOLPREFIX)gcc
+CXX=$(TOOLPREFIX)g++
 
 OPTFLAGS = -O2 -ffast-math
 
-CFLAGS = $(OPTFLAGS) -I$(VAMP_SDK_DIR) -Wall 
-CXXFLAGS = $(OPTFLAGS) -I$(VAMP_SDK_DIR) -I../boost_1_44_0 -Wall
+CFLAGS += $(OPTFLAGS) -I$(VAMP_SDK_DIR) -Wall 
+CXXFLAGS += $(OPTFLAGS) -I$(VAMP_SDK_DIR) -I../boost_1_44_0 -Wall
 
 PLUGIN_EXT = .dll
 PLUGIN = $(PLUGIN_LIBRARY_NAME)$(PLUGIN_EXT)
 
-LDFLAGS = -shared -fno-exceptions -static-libgcc -Wl,-soname=$(PLUGIN) $(VAMP_SDK_DIR)/libvamp-sdk.a -Wl,--version-script=vamp-plugin.map 
+LDFLAGS += -shared -static -fno-exceptions -static-libgcc -Wl,-soname=$(PLUGIN) $(VAMP_SDK_DIR)/libvamp-sdk.a -Wl,--retain-symbols-file=vamp-plugin.list
 
 
 $(PLUGIN): $(PLUGIN_CODE_OBJECTS)
 	   $(CXX) -o $@ $^ $(LDFLAGS)
 
 clean:
-	del *.o
+	$(RM) *.o
 
--- a/Makefile.osx	Thu Sep 05 17:30:38 2013 +0100
+++ b/Makefile.osx	Mon Nov 02 11:32:30 2015 +0000
@@ -12,12 +12,12 @@
 
 
 ##  Uncomment these for an OS/X native build using command-line tools:
-ARCHFLAGS = -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk -mmacosx-version-min=10.5 -arch i386 -arch x86_64 
-CFLAGS = $(ARCHFLAGS) -Wall -fPIC -g -O3
-CXXFLAGS = $(CFLAGS) -I$(VAMP_SDK_DIR) -I$(BOOST_ROOT) -I.
+ARCHFLAGS ?= -isysroot /Developer/SDKs/MacOSX10.6.sdk -mmacosx-version-min=10.6 -arch i386
+CFLAGS += $(ARCHFLAGS) -Wall -fPIC -g -O3
+CXXFLAGS += $(CFLAGS) -I$(VAMP_SDK_DIR) -I$(BOOST_ROOT)
 PLUGIN_EXT = .dylib
 PLUGIN = $(PLUGIN_LIBRARY_NAME)$(PLUGIN_EXT)
-LDFLAGS = $(ARCHFLAGS) -dynamiclib -install_name $(PLUGIN) $(VAMP_SDK_DIR)/libvamp-sdk.a  -exported_symbols_list vamp-plugin.list -framework Accelerate
+LDFLAGS += $(ARCHFLAGS) -dynamiclib -install_name $(PLUGIN) $(VAMP_SDK_DIR)/libvamp-sdk.a  -exported_symbols_list vamp-plugin.list -framework Accelerate
 
 
 $(PLUGIN): $(PLUGIN_CODE_OBJECTS)
--- a/NNLSBase.cpp	Thu Sep 05 17:30:38 2013 +0100
+++ b/NNLSBase.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -81,7 +81,7 @@
     if (debug_on) cerr << "--> getPluginVersion" << endl;
     // Increment this each time you release a version that behaves
     // differently from the previous one
-    return 3;
+    return 5;
 }
 
 string
@@ -267,6 +267,8 @@
 void
 NNLSBase::setParameter(string identifier, float value) 
 {
+//    cerr << "setParameter (" << identifier << ") -> " << value << endl;
+    
     if (debug_on) cerr << "--> setParameter" << endl;
     if (identifier == "useNNLS") {
         m_useNNLS = (int) value;
--- a/NNLSChroma.cpp	Thu Sep 05 17:30:38 2013 +0100
+++ b/NNLSChroma.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -84,6 +84,9 @@
     
     int index = 0;
 
+    float featureRate =
+        (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+
     OutputDescriptor logfreqspecOutput;
     logfreqspecOutput.identifier = "logfreqspec";
     logfreqspecOutput.name = "Log-Frequency Spectrum";
@@ -95,7 +98,7 @@
     logfreqspecOutput.isQuantized = false;
     logfreqspecOutput.sampleType = OutputDescriptor::FixedSampleRate;
     logfreqspecOutput.hasDuration = false;
-    logfreqspecOutput.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    logfreqspecOutput.sampleRate = featureRate;
     list.push_back(logfreqspecOutput);
     m_outputLogfreqspec = index++;
 
@@ -110,7 +113,7 @@
     tunedlogfreqspecOutput.isQuantized = false;
     tunedlogfreqspecOutput.sampleType = OutputDescriptor::FixedSampleRate;
     tunedlogfreqspecOutput.hasDuration = false;
-    tunedlogfreqspecOutput.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    tunedlogfreqspecOutput.sampleRate = featureRate;
     list.push_back(tunedlogfreqspecOutput);
     m_outputTunedlogfreqspec = index++;
     
@@ -125,7 +128,7 @@
     semitonespectrumOutput.isQuantized = false;
     semitonespectrumOutput.sampleType = OutputDescriptor::FixedSampleRate;
     semitonespectrumOutput.hasDuration = false;
-    semitonespectrumOutput.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    semitonespectrumOutput.sampleRate = featureRate;
     list.push_back(semitonespectrumOutput);
     m_outputSemitonespectrum = index++;
     
@@ -141,7 +144,7 @@
     chromaOutput.isQuantized = false;
     chromaOutput.sampleType = OutputDescriptor::FixedSampleRate;
     chromaOutput.hasDuration = false;
-    chromaOutput.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    chromaOutput.sampleRate = featureRate;
     list.push_back(chromaOutput);
     m_outputChroma = index++;
     
@@ -157,7 +160,7 @@
     basschromaOutput.isQuantized = false;
     basschromaOutput.sampleType = OutputDescriptor::FixedSampleRate;
     basschromaOutput.hasDuration = false;
-    basschromaOutput.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    basschromaOutput.sampleRate = featureRate;
     list.push_back(basschromaOutput);
     m_outputBasschroma = index++;
     
@@ -173,7 +176,7 @@
     bothchromaOutput.isQuantized = false;
     bothchromaOutput.sampleType = OutputDescriptor::FixedSampleRate;
     bothchromaOutput.hasDuration = false;
-    bothchromaOutput.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
+    bothchromaOutput.sampleRate = featureRate;
     list.push_back(bothchromaOutput);
     m_outputBothchroma = index++;    
     return list;
@@ -244,7 +247,7 @@
         calculate a tuned log-frequency spectrogram (f2): use the tuning estimated above (kinda f0) to 
         perform linear interpolation on the existing log-frequency spectrogram (kinda f1).
     **/
-    cerr << endl << "[NNLS Chroma Plugin] Tuning Log-Frequency Spectrogram ... ";
+    if (debug_on) cerr << endl << "[NNLS Chroma Plugin] Tuning Log-Frequency Spectrogram ... ";
 					
     float tempValue = 0;
 
@@ -294,7 +297,7 @@
         fsOut[m_outputTunedlogfreqspec].push_back(f2);
         count++;
     }
-    cerr << "done." << endl;
+    if (debug_on) cerr << "done." << endl;
 	    
     /** Semitone spectrum and chromagrams
         Semitone-spaced log-frequency spectrum derived from the tuned log-freq spectrum above. the spectrum
@@ -303,9 +306,9 @@
         bass and treble stacked onto each other).
     **/
     if (m_useNNLS == 0) {
-        cerr << "[NNLS Chroma Plugin] Mapping to semitone spectrum and chroma ... ";
+        if (debug_on) cerr << "[NNLS Chroma Plugin] Mapping to semitone spectrum and chroma ... ";
     } else {
-        cerr << "[NNLS Chroma Plugin] Performing NNLS and mapping to chroma ... ";
+        if (debug_on) cerr << "[NNLS Chroma Plugin] Performing NNLS and mapping to chroma ... ";
     }
 
 	    
@@ -471,7 +474,7 @@
         fsOut[m_outputBothchroma].push_back(f6);
         count++;
     }
-    cerr << "done." << endl;
+    if (debug_on) cerr << "done." << endl;
 
     return fsOut;     
 
--- a/README	Thu Sep 05 17:30:38 2013 +0100
+++ b/README	Mon Nov 02 11:32:30 2015 +0000
@@ -78,5 +78,25 @@
 
 ### References and Credits ###
 
+If you make use of this software for any public or commercial purpose,
+we ask you to kindly mention the authors and Queen Mary, University of
+London in your user-visible documentation. We're very happy to see
+this sort of use, but would much appreciate being credited, separately
+from the requirements of the software license itself (see below).
+
+If you make use of this software for academic purposes, please cite:
+
 Mauch, Matthias and Dixon, Simon: [*Approximate Note Transcription for the Improved Identification of Difficult Chords*](http://schall-und-mauch.de/artificialmusicality/?p=89), Proceedings of the 11th International Society for Music Information Retrieval Conference (ISMIR 2010), 2010.
 
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or (at
+your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/Tuning.cpp	Thu Sep 05 17:30:38 2013 +0100
+++ b/Tuning.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -126,8 +126,8 @@
     d10.maxValue = 452.89;
     d10.isQuantized = false;
     d10.sampleType = OutputDescriptor::FixedSampleRate;
+    d10.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
     d10.hasDuration = false;
-    // d10.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize;
     list.push_back(d10);
     m_outputLocalTuning = index++;
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chordextract.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -0,0 +1,149 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+  NNLS-Chroma / Chordino
+
+  Audio feature extraction plugins for chromagram and chord
+  estimation.
+
+  Centre for Digital Music, Queen Mary University of London.
+  This file copyright 2014 QMUL.
+    
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.  See the file
+  COPYING included with this distribution for more information.
+*/
+
+/*
+  Extract chords from an audio file, read using libsndfile.  Works by
+  constructing the plugin as a C++ class directly, and using plugin
+  adapters from the Vamp Host SDK to provide input.
+
+  You can compile this with e.g. the following (Linux example):
+
+  $ g++ -D_VAMP_PLUGIN_IN_HOST_NAMESPACE -O2 -ffast-math chordextract.cpp Chordino.cpp NNLSBase.cpp chromamethods.cpp viterbi.cpp nnls.c -o chordextract -lsndfile -lvamp-hostsdk -ldl
+
+  But the same idea should work on any platform, so long as the Boost
+  Tokenizer headers and the Vamp Host SDK library are available and
+  the _VAMP_PLUGIN_IN_HOST_NAMESPACE preprocessor symbol is defined
+  throughout.
+*/
+
+#define _VAMP_PLUGIN_IN_HOST_NAMESPACE 1
+
+#include <vamp-hostsdk/PluginInputDomainAdapter.h>
+#include <vamp-hostsdk/PluginBufferingAdapter.h>
+
+#include "Chordino.h"
+
+#include <sndfile.h>
+
+#include <iostream>
+#include <string>
+
+using namespace std;
+using namespace Vamp;
+using namespace Vamp::HostExt;
+
+int main(int argc, char **argv)
+{
+    const char *myname = argv[0];
+
+    if (argc != 2) {
+	cerr << "usage: " << myname << " file.wav" << endl;
+	return 2;
+    }
+
+    const char *infile = argv[1];
+
+    SF_INFO sfinfo;
+    SNDFILE *sndfile = sf_open(infile, SFM_READ, &sfinfo);
+
+    if (!sndfile) {
+	cerr << myname << ": Failed to open input file " << infile
+	     << ": " << sf_strerror(sndfile) << endl;
+	return 1;
+    }
+
+    Chordino *chordino = new Chordino(sfinfo.samplerate);
+    PluginInputDomainAdapter *ia = new PluginInputDomainAdapter(chordino);
+    ia->setProcessTimestampMethod(PluginInputDomainAdapter::ShiftData);
+    PluginBufferingAdapter *adapter = new PluginBufferingAdapter(ia);
+
+    int blocksize = adapter->getPreferredBlockSize();
+
+    // Plugin requires 1 channel (we will mix down)
+    if (!adapter->initialise(1, blocksize, blocksize)) {
+	cerr << myname << ": Failed to initialise Chordino adapter!" << endl;
+	return 1;
+    }
+
+    float *filebuf = new float[sfinfo.channels * blocksize];
+    float *mixbuf = new float[blocksize];
+
+    Plugin::FeatureList chordFeatures;
+    Plugin::FeatureSet fs;
+
+    int chordFeatureNo = -1;
+    Plugin::OutputList outputs = adapter->getOutputDescriptors();
+    for (int i = 0; i < int(outputs.size()); ++i) {
+	if (outputs[i].identifier == "simplechord") {
+	    chordFeatureNo = i;
+	}
+    }
+    if (chordFeatureNo < 0) {
+	cerr << myname << ": Failed to identify chords output!" << endl;
+	return 1;
+    }
+    
+    int frame = 0;
+    while (frame < sfinfo.frames) {
+
+	int count = -1;
+	if ((count = sf_readf_float(sndfile, filebuf, blocksize)) <= 0) break;
+
+	// mix down
+	for (int i = 0; i < blocksize; ++i) {
+	    mixbuf[i] = 0.f;
+	    if (i < count) {
+		for (int c = 0; c < sfinfo.channels; ++c) {
+		    mixbuf[i] += filebuf[i * sfinfo.channels + c] / sfinfo.channels;
+		}
+	    }
+	}
+
+	RealTime timestamp = RealTime::frame2RealTime(frame, sfinfo.samplerate);
+	
+	// feed to plugin: can just take address of buffer, as only one channel
+	fs = adapter->process(&mixbuf, timestamp);
+
+	chordFeatures.insert(chordFeatures.end(),
+			     fs[chordFeatureNo].begin(),
+			     fs[chordFeatureNo].end());
+
+	frame += count;
+    }
+
+    sf_close(sndfile);
+
+    // features at end of processing (actually Chordino does all its work here)
+    fs = adapter->getRemainingFeatures();
+
+    // chord output is output index 0
+    chordFeatures.insert(chordFeatures.end(),
+			 fs[chordFeatureNo].begin(),
+			 fs[chordFeatureNo].end());
+
+    for (int i = 0; i < (int)chordFeatures.size(); ++i) {
+	cout << chordFeatures[i].timestamp.toString() << ": "
+	     << chordFeatures[i].label << endl;
+    }
+
+    delete[] filebuf;
+    delete[] mixbuf;
+    
+    delete adapter;
+}
+
--- a/chromamethods.cpp	Thu Sep 05 17:30:38 2013 +0100
+++ b/chromamethods.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -373,18 +373,19 @@
 	int ppathsize = static_cast<int>(ppath.size());
     for (int i = 0; i < ppathsize; ++i) {
     	chordDictFilename = ppath[i] + "/" + chordDictBase;
-    	cerr << "Looking for chord.dict in " << chordDictFilename << "..." ;
+//    	cerr << "Looking for chord.dict in " << chordDictFilename << "..." ;
     	fstream fin;
         fin.open(chordDictFilename.c_str(),ios::in);
         if( fin.is_open() )
         {
             fin.close();
-    	    cerr << " success." << endl;
+//    	    cerr << " success." << endl;
     	    break;
         } else {
-            if (i+1 < ppathsize) cerr << " (not found yet) ..." << endl;
-            else {
-                cerr << "* WARNING: failed to find chord dictionary, using default chord dictionary." << endl;
+            if (i+1 < ppathsize) {
+//                cerr << " (not found yet) ..." << endl;
+            } else {
+//                cerr << "* WARNING: failed to find chord dictionary, using default chord dictionary." << endl;
                 hasExternalDictinoary = false;                
             } 
         }
--- a/nnls-chroma.n3	Thu Sep 05 17:30:38 2013 +0100
+++ b/nnls-chroma.n3	Mon Nov 02 11:32:30 2015 +0000
@@ -6,46 +6,58 @@
 @prefix dc:       <http://purl.org/dc/elements/1.1/> .
 @prefix af:       <http://purl.org/ontology/af/> .
 @prefix foaf:     <http://xmlns.com/foaf/0.1/> .
+@prefix doap: 	  <http://usefulinc.com/ns/doap#> .
 @prefix cc:       <http://web.resource.org/cc/> .
 @prefix :         <#> .
 
 <>  a   vamp:PluginDescription ;
     foaf:maker          <http://www.vamp-plugins.org/doap.rdf#template-generator> ;
-    foaf:primaryTopic   <http://vamp-plugins.org/rdf/plugins/nnls-chromannls-chroma> .
+    foaf:primaryTopic   <http://vamp-plugins.org/rdf/plugins/nnls-chroma> .
+
+:maker
+    foaf:name "Matthias Mauch" ;
+    foaf:logo <http://vamp-plugins.org/rdf/plugins/makers/qm.png> ;
+    foaf:page <http://c4dm.eecs.qmul.ac.uk/> .
 
 :nnls-chroma a  vamp:PluginLibrary ;
-    vamp:identifier "nnls-chroma"  ; 
+    vamp:identifier "nnls-chroma"  ;
+    dc:title "Chordino and NNLS Chroma" ;
+    dc:description "Harmony and chord extraction plugins by Matthias Mauch at C4DM" ;
     vamp:available_plugin plugbase:chordino ; 
     vamp:available_plugin plugbase:nnls-chroma ; 
-    vamp:available_plugin plugbase:tuning ; 
-#   foaf:page <Place more-information HTML page URL here and uncomment> ;
+    vamp:available_plugin plugbase:tuning ;  
+    foaf:page <http://www.isophonics.net/nnls-chroma> ;
+    doap:download-page <http://www.isophonics.net/nnls-chroma> ;
+    foaf:page <http://www.omras2.org/> ;
+    foaf:page <http://www.matthiasmauch.net/> ; 
+    foaf:maker :maker ;
+    vamp:has_source true ;
+    vamp:has_binary "win32" ;
+    vamp:has_binary "osx" ;
     .
 
 plugbase:chordino a   vamp:Plugin ;
     dc:title              "Chordino" ;
     vamp:name             "Chordino" ;
-    dc:description        """Chordino provides a simple chord transcription based on NNLS Chroma (as in the NNLS Chroma plugin). Chord profiles given by the user in the file chord.dict are used to calculate frame-wise chord similarities. A simple (non-state-of-the-art!) algorithm smoothes these to provide a chord transcription using a standard HMM/Viterbi approach.""" ;
-    foaf:maker            [ foaf:name "Matthias Mauch" ] ; # FIXME could give plugin author's URI here
+    dc:description        """Chordino provides a simple chord transcription based on NNLS Chroma (as in the NNLS Chroma plugin). Chord profiles given by the user in the file chord.dict are used to calculate frame-wise chord similarities. Two simple (non-state-of-the-art!) algorithms are available that smooth these to provide a chord transcription: a simple chord change method, and a standard HMM/Viterbi approach.""" ;
+    foaf:maker            :maker ;
     dc:rights             """GPL""" ;
 #   cc:license            <Place plugin license URI here and uncomment> ; 
     vamp:identifier       "chordino" ;
     vamp:vamp_API_version vamp:api_version_2 ;
-    owl:versionInfo       "3" ;
+    owl:versionInfo       "1" ;
     vamp:input_domain     vamp:FrequencyDomain ;
 
 
     vamp:parameter   plugbase:chordino_param_useNNLS ;
+    vamp:parameter   plugbase:chordino_param_useHMM ;
     vamp:parameter   plugbase:chordino_param_rollon ;
     vamp:parameter   plugbase:chordino_param_tuningmode ;
     vamp:parameter   plugbase:chordino_param_whitening ;
-    vamp:parameter   plugbase:chordino_param_spectralshape ;
-    vamp:parameter   plugbase:chordino_param_boostn ;
-    vamp:parameter   plugbase:chordino_param_usehartesyntax ;
+    vamp:parameter   plugbase:chordino_param_s ;
 
     vamp:output      plugbase:chordino_output_simplechord ;
-    vamp:output      plugbase:chordino_output_chordnotes ;
     vamp:output      plugbase:chordino_output_harmonicchange ;
-    vamp:output      plugbase:chordino_output_loglikelihood ;
     .
 plugbase:chordino_param_useNNLS a  vamp:QuantizedParameter ;
     vamp:identifier     "useNNLS" ;
@@ -58,10 +70,21 @@
     vamp:default_value   1 ;
     vamp:value_names     ();
     .
+plugbase:chordino_param_useHMM a  vamp:QuantizedParameter ;
+    vamp:identifier     "useHMM" ;
+    dc:title            "HMM (Viterbi decoding)" ;
+    dc:format           "" ;
+    vamp:min_value       0 ;
+    vamp:max_value       1 ;
+    vamp:unit           "" ;
+    vamp:quantize_step   1  ;
+    vamp:default_value   1 ;
+    vamp:value_names     ();
+    .
 plugbase:chordino_param_rollon a  vamp:QuantizedParameter ;
     vamp:identifier     "rollon" ;
-    dc:title            "bass noise threshold" ;
-    dc:format           "%" ;
+    dc:title            "spectral roll-on" ;
+    dc:format           "" ;
     vamp:min_value       0 ;
     vamp:max_value       5 ;
     vamp:unit           "%" ;
@@ -90,8 +113,8 @@
     vamp:default_value   1 ;
     vamp:value_names     ();
     .
-plugbase:chordino_param_spectralshape a  vamp:Parameter ;
-    vamp:identifier     "spectralshape" ;
+plugbase:chordino_param_s a  vamp:Parameter ;
+    vamp:identifier     "s" ;
     dc:title            "spectral shape" ;
     dc:format           "" ;
     vamp:min_value       0.5 ;
@@ -100,57 +123,18 @@
     vamp:default_value   0.7 ;
     vamp:value_names     ();
     .
-plugbase:chordino_param_boostn a  vamp:Parameter ;
-    vamp:identifier     "boostn" ;
-    dc:title            "boost N" ;
-    dc:format           "" ;
-    vamp:min_value       0 ;
-    vamp:max_value       1 ;
-    vamp:unit           ""  ;
-    vamp:default_value   0.1 ;
-    vamp:value_names     ();
-    .
-plugbase:chordino_param_usehartesyntax a  vamp:QuantizedParameter ;
-    vamp:identifier     "usehartesyntax" ;
-    dc:title            "use Harte syntax" ;
-    dc:format           "" ;
-    vamp:min_value       0 ;
-    vamp:max_value       1 ;
-    vamp:unit           "" ;
-    vamp:quantize_step   1  ;
-    vamp:default_value   0 ;
-    vamp:value_names     ( "no" "yes");
-    .
 plugbase:chordino_output_simplechord a  vamp:SparseOutput ;
     vamp:identifier       "simplechord" ;
     dc:title              "Chord Estimate" ;
-    dc:description        """Estimated chord times and labels."""  ;
+    dc:description        """Estimated chord times and labels. Two simple (non-state-of-the-art!) algorithms are available that smooth these to provide a chord transcription: a simple chord change method, and a standard HMM/Viterbi approach."""  ;
     vamp:fixed_bin_count  "true" ;
-    vamp:unit             "" ;
+    vamp:unit             "chord" ;
+    a                     vamp:QuantizedOutput ;
+    vamp:quantize_step    1  ;
     vamp:bin_count        0 ;
     vamp:sample_type      vamp:VariableSampleRate ;
     vamp:sample_rate      21.5332 ;
-    vamp:computes_event_type   af:ChordSegment ;    
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
-    .
-plugbase:chordino_output_chordnotes a  vamp:SparseOutput ;
-    vamp:identifier       "chordnotes" ;
-    dc:title              "Note Representation of Chord Estimate" ;
-    dc:description        """A simple represenation of the estimated chord with bass note (if applicable) and chord notes."""  ;
-    vamp:fixed_bin_count  "true" ;
-    vamp:unit             "MIDI units" ;
-    a                     vamp:QuantizedOutput ;
-    vamp:quantize_step    1  ;
-    a                 vamp:KnownExtentsOutput ;
-    vamp:min_value    0  ;
-    vamp:max_value    127  ;
-    vamp:bin_count        1 ;
-    vamp:sample_type      vamp:VariableSampleRate ;
-    vamp:sample_rate      21.5332 ;
-    vamp:computes_event_type   af:Note ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
+    vamp:computes_event_type   af:ChordSegment ;
     .
 plugbase:chordino_output_harmonicchange a  vamp:DenseOutput ;
     vamp:identifier       "harmonicchange" ;
@@ -158,32 +142,22 @@
     dc:description        """An indication of the likelihood of harmonic change. Depends on the chord dictionary. Calculation is different depending on whether the Viterbi algorithm is used for chord estimation, or the simple chord estimate."""  ;
     vamp:fixed_bin_count  "true" ;
     vamp:unit             "" ;
+    a                 vamp:KnownExtentsOutput ;
+    vamp:min_value    0  ;
+    vamp:max_value    0.999  ;
     vamp:bin_count        1 ;
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
-    .
-plugbase:chordino_output_loglikelihood a  vamp:DenseOutput ;
-    vamp:identifier       "loglikelihood" ;
-    dc:title              "Log-Likelihood of Chord Estimate" ;
-    dc:description        """Logarithm of the likelihood value of the simple chord estimate."""  ;
-    vamp:fixed_bin_count  "true" ;
-    vamp:unit             "" ;
-    vamp:bin_count        1 ;
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
+    vamp:computes_signal_type  af:TonalChangeDetectionFunction;
     .
 plugbase:nnls-chroma a   vamp:Plugin ;
     dc:title              "NNLS Chroma" ;
     vamp:name             "NNLS Chroma" ;
     dc:description        """This plugin provides a number of features derived from a DFT-based log-frequency amplitude spectrum: some variants of the log-frequency spectrum, including a semitone spectrum derived from approximate transcription using the NNLS algorithm; and based on this semitone spectrum, different chroma features.""" ;
-    foaf:maker            [ foaf:name "Matthias Mauch" ] ; # FIXME could give plugin author's URI here
+    foaf:maker            :maker ;
     dc:rights             """GPL""" ;
 #   cc:license            <Place plugin license URI here and uncomment> ; 
     vamp:identifier       "nnls-chroma" ;
     vamp:vamp_API_version vamp:api_version_2 ;
-    owl:versionInfo       "3" ;
+    owl:versionInfo       "1" ;
     vamp:input_domain     vamp:FrequencyDomain ;
 
 
@@ -214,8 +188,8 @@
     .
 plugbase:nnls-chroma_param_rollon a  vamp:QuantizedParameter ;
     vamp:identifier     "rollon" ;
-    dc:title            "bass noise threshold" ;
-    dc:format           "%" ;
+    dc:title            "spectral roll-on" ;
+    dc:format           "" ;
     vamp:min_value       0 ;
     vamp:max_value       5 ;
     vamp:unit           "%" ;
@@ -272,9 +246,7 @@
     vamp:fixed_bin_count  "true" ;
     vamp:unit             "" ;
     vamp:bin_count        256 ;
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
+    vamp:computes_signal_type  af:Spectrogram ;    
     .
 plugbase:nnls-chroma_output_tunedlogfreqspec a  vamp:DenseOutput ;
     vamp:identifier       "tunedlogfreqspec" ;
@@ -283,9 +255,7 @@
     vamp:fixed_bin_count  "true" ;
     vamp:unit             "" ;
     vamp:bin_count        256 ;
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
+    vamp:computes_signal_type  af:Spectrogram ;
     .
 plugbase:nnls-chroma_output_semitonespectrum a  vamp:DenseOutput ;
     vamp:identifier       "semitonespectrum" ;
@@ -294,9 +264,7 @@
     vamp:fixed_bin_count  "true" ;
     vamp:unit             "" ;
     vamp:bin_count        84 ;
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
+    vamp:computes_signal_type  af:Spectrogram ;    
     .
 plugbase:nnls-chroma_output_chroma a  vamp:DenseOutput ;
     vamp:identifier       "chroma" ;
@@ -306,8 +274,6 @@
     vamp:unit             "" ;
     vamp:bin_count        12 ;
     vamp:bin_names        ( "A" "Bb" "B" "C" "C#" "D" "Eb" "E" "F" "F#" "G" "Ab");
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
     vamp:computes_signal_type  af:Chromagram ;
     .
 plugbase:nnls-chroma_output_basschroma a  vamp:DenseOutput ;
@@ -318,8 +284,6 @@
     vamp:unit             "" ;
     vamp:bin_count        12 ;
     vamp:bin_names        ( "A" "Bb" "B" "C" "C#" "D" "Eb" "E" "F" "F#" "G" "Ab");
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
     vamp:computes_signal_type  af:Chromagram ;
     .
 plugbase:nnls-chroma_output_bothchroma a  vamp:DenseOutput ;
@@ -330,20 +294,18 @@
     vamp:unit             "" ;
     vamp:bin_count        24 ;
     vamp:bin_names        ( "A  (bass)" "Bb (bass)" "B  (bass)" "C  (bass)" "C# (bass)" "D  (bass)" "Eb (bass)" "E  (bass)" "F  (bass)" "F# (bass)" "G  (bass)" "Ab (bass)" "A" "Bb" "B" "C" "C#" "D" "Eb" "E" "F" "F#" "G" "Ab");
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
     vamp:computes_signal_type  af:Chromagram ;
     .
 plugbase:tuning a   vamp:Plugin ;
     dc:title              "Tuning" ;
     vamp:name             "Tuning" ;
     dc:description        """The tuning plugin can estimate the local and global tuning of piece. The same tuning method is used for the NNLS Chroma and Chordino plugins.""" ;
-    foaf:maker            [ foaf:name "Matthias Mauch" ] ; # FIXME could give plugin author's URI here
+    foaf:maker            :maker ;
     dc:rights             """GPL""" ;
 #   cc:license            <Place plugin license URI here and uncomment> ; 
     vamp:identifier       "tuning" ;
     vamp:vamp_API_version vamp:api_version_2 ;
-    owl:versionInfo       "3" ;
+    owl:versionInfo       "1" ;
     vamp:input_domain     vamp:FrequencyDomain ;
 
 
@@ -354,8 +316,8 @@
     .
 plugbase:tuning_param_rollon a  vamp:QuantizedParameter ;
     vamp:identifier     "rollon" ;
-    dc:title            "bass noise threshold" ;
-    dc:format           "%" ;
+    dc:title            "spectral roll-on" ;
+    dc:format           "" ;
     vamp:min_value       0 ;
     vamp:max_value       5 ;
     vamp:unit           "%" ;
@@ -372,12 +334,10 @@
     a                 vamp:KnownExtentsOutput ;
     vamp:min_value    427.47  ;
     vamp:max_value    452.89  ;
-    vamp:bin_count        1 ;
+    vamp:bin_count        0 ;
     vamp:sample_type      vamp:VariableSampleRate ;
-    vamp:sample_rate      1.47994e-39 ;
-#   vamp:computes_event_type   <Place event type URI here and uncomment> ;
-#   vamp:computes_feature      <Place feature attribute URI here and uncomment> ;
-#   vamp:computes_signal_type  <Place signal type URI here and uncomment> ;
+    vamp:sample_rate      2.38221e-44 ;
+    vamp:computes_event_type   af:MusicSegment;
     .
 plugbase:tuning_output_localtuning a  vamp:DenseOutput ;
     vamp:identifier       "localtuning" ;
--- a/viterbi.cpp	Thu Sep 05 17:30:38 2013 +0100
+++ b/viterbi.cpp	Mon Nov 02 11:32:30 2015 +0000
@@ -20,11 +20,14 @@
     /* initialise first frame */
     for (int iState = 0; iState < nState; ++iState) {
         delta[iState] = init[iState] * obs[0][iState];
+//	cerr << "init[" << iState << "] = " << init[iState] << ", obs[0][" << iState << "] = " << obs[0][iState] << endl;
         deltasum += delta[iState];
     }
     for (int iState = 0; iState < nState; ++iState) delta[iState] /= deltasum; // normalise (scale)
     scale->push_back(1.0/deltasum);
     psi.push_back(vector<int>(nState,0));
+
+//    cerr << "nState = " << nState << ", deltasum = " << deltasum << endl;
     
     /* rest of the forward step */
     for (int iFrame = 1; iFrame < nFrame; ++iFrame) {