[med-svn] [praat] 02/05: Imported Upstream version 6.0.19

Rafael Laboissière rlaboiss-guest at moszumanska.debian.org
Mon Jun 27 21:48:53 UTC 2016


This is an automated email from the git hooks/post-receive script.

rlaboiss-guest pushed a commit to branch master
in repository praat.

commit 0993113f228108e1ef914f21ee0dcb9e76e4d5aa
Author: Rafael Laboissiere <rafael at laboissiere.net>
Date:   Sun Jun 26 17:32:44 2016 -0300

    Imported Upstream version 6.0.19
---
 FFNet/FFNet.cpp                                    |   6 +-
 ...ies.cpp => FFNet_ActivationList_Categories.cpp} |  14 +-
 ...egories.h => FFNet_ActivationList_Categories.h} |  16 +-
 FFNet/{FFNet_Pattern.cpp => FFNet_PatternList.cpp} |  10 +-
 FFNet/{FFNet_Pattern.h => FFNet_PatternList.h}     |  12 +-
 ...on.cpp => FFNet_PatternList_ActivationList.cpp} |  52 +--
 ...vation.h => FFNet_PatternList_ActivationList.h} |  24 +-
 FFNet/FFNet_PatternList_Categories.cpp             |  99 +++++
 ...Categories.h => FFNet_PatternList_Categories.h} |  24 +-
 FFNet/FFNet_Pattern_Categories.cpp                 |  99 -----
 FFNet/Makefile                                     |  10 +-
 FFNet/RBM_extensions.cpp                           |  38 ++
 contrib/ola/KNN_def.h => FFNet/RBM_extensions.h    |  30 +-
 FFNet/manual_FFNet.cpp                             | 122 +++---
 FFNet/praat_FFNet_init.cpp                         | 180 +++++----
 LPC/LPC_and_LineSpectralFrequencies.cpp            | 305 ++++++++++++++
 LPC/LPC_and_LineSpectralFrequencies.h              |  30 ++
 LPC/LPC_and_Polynomial.cpp                         |   4 +-
 LPC/LPC_to_Spectrum.cpp                            |   3 +-
 LPC/LineSpectralFrequencies.cpp                    | 140 +++++++
 LPC/LineSpectralFrequencies.h                      |  50 +++
 LPC/LineSpectralFrequencies_def.h                  |  47 +++
 LPC/Makefile                                       |   4 +-
 LPC/Sound_and_LPC.cpp                              |  14 +-
 LPC/manual_LPC.cpp                                 |   6 +-
 LPC/praat_LPC_init.cpp                             |  47 ++-
 contrib/ola/FeatureWeights.cpp                     |  10 +-
 contrib/ola/FeatureWeights.h                       |   6 +-
 contrib/ola/KNN.cpp                                |  64 +--
 contrib/ola/KNN.h                                  |  93 ++---
 contrib/ola/KNN_def.h                              |   2 +-
 contrib/ola/KNN_prune.cpp                          |  10 +-
 contrib/ola/KNN_prune.h                            |  10 +-
 contrib/ola/Pattern_to_Categories_cluster.cpp      |   6 +-
 contrib/ola/Pattern_to_Categories_cluster.h        |  10 +-
 contrib/ola/manual_KNN.cpp                         | 112 +++---
 contrib/ola/praat_contrib_Ola_KNN.cpp              | 152 +++----
 dwsys/NUM2.cpp                                     |  50 ++-
 dwsys/NUM2.h                                       |  11 +-
 dwtest/English_default.SpeechSynthesizer           |  14 +
 dwtest/espeakdata_voices_names_1.47.04.Strings     |  89 +++++
 dwtest/espeakdata_voices_names_1.48.04.Strings     |  90 +++++
 dwtest/iris_4-2-3-3.FFNet                          | Bin 0 -> 309 bytes
 dwtest/old_type.Activation                         | Bin 0 -> 8097 bytes
 dwtest/old_type.Pattern                            | Bin 0 -> 8094 bytes
 dwtest/speechsynthesizer_test.praat                |  73 ++++
 dwtest/test_Activation.praat                       |  25 --
 dwtest/test_ActivationList.praat                   |  34 ++
 dwtest/test_LineSpectralFrequencies.praat          |  32 ++
 dwtest/test_PatternList.praat                      |  34 ++
 dwtest/test_Polynomial.praat                       |  67 ++++
 dwtest/test_SpeechSynthesizer.praat                |  21 +-
 dwtools/{Activation.cpp => ActivationList.cpp}     |  24 +-
 dwtools/{Activation.h => ActivationList.h}         |  20 +-
 ...cpp => Discriminant_PatternList_Categories.cpp} |  14 +-
 ...ies.h => Discriminant_PatternList_Categories.h} |  26 +-
 dwtools/Eigen_and_Matrix.h                         |   2 +-
 dwtools/Excitations.cpp                            |   6 +-
 dwtools/Excitations.h                              |   4 +-
 dwtools/Makefile                                   |   6 +-
 dwtools/{Pattern.cpp => PatternList.cpp}           |  46 ++-
 dwtools/{Pattern.h => PatternList.h}               |  25 +-
 dwtools/Polynomial.cpp                             | 230 ++++++++++-
 dwtools/Polynomial.h                               |  48 ++-
 dwtools/Polynomial_def.h                           |  10 +-
 dwtools/Spectrogram_extensions.cpp                 |  14 +-
 dwtools/TableOfReal_extensions.cpp                 |   8 +-
 dwtools/TableOfReal_extensions.h                   |   6 +-
 dwtools/Table_extensions.h                         |   1 -
 dwtools/manual_dwtools.cpp                         |  40 +-
 dwtools/praat_David_init.cpp                       | 271 ++++++++++---
 dwtools/praat_HMM_init.cpp                         |   4 +-
 fon/manual_tutorials.cpp                           |   6 +-
 fon/praat_Fon.cpp                                  |   4 +-
 gram/RBM.cpp                                       |   8 +-
 gram/RBM.h                                         |   8 +-
 gram/praat_gram.cpp                                |  24 +-
 stat/Table.cpp                                     |   8 +-
 sys/Formula.cpp                                    | 440 ++++++++++++++-------
 sys/Formula.h                                      |  31 +-
 sys/Graphics_colour.cpp                            |   4 +-
 sys/Interpreter.cpp                                | 371 ++++++++++++-----
 sys/Interpreter.h                                  |  16 +-
 sys/Strings.cpp                                    |   8 +-
 sys/melder.h                                       |  10 +-
 sys/melder_files.cpp                               |   8 +-
 sys/praat_objectMenus.cpp                          |   4 +-
 sys/praat_version.h                                |   8 +-
 test/script/arrays.praat                           |  58 +--
 test/script/indexedVariables.praat                 |  47 +++
 90 files changed, 3023 insertions(+), 1246 deletions(-)

diff --git a/FFNet/FFNet.cpp b/FFNet/FFNet.cpp
index 1f05fdb..cf9127e 100644
--- a/FFNet/FFNet.cpp
+++ b/FFNet/FFNet.cpp
@@ -36,7 +36,7 @@
 #include "FFNet_Matrix.h"
 #include "Matrix_extensions.h"
 #include "TableOfReal_extensions.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "Collection.h"
 #include "Categories.h"
 
@@ -741,9 +741,9 @@ autoCollection FFNet_createIrisExample (long numberOfHidden1, long numberOfHidde
 			}
 		}
 
-		autoPattern ap;
+		autoPatternList ap;
 		autoCategories ac;
-		TableOfReal_to_Pattern_and_Categories (iris.get(), 0, 0, 0, 0, & ap, & ac);
+		TableOfReal_to_PatternList_and_Categories (iris.get(), 0, 0, 0, 0, & ap, & ac);
 		Thing_setName (ap.get(), U"iris");
 		Thing_setName (ac.get(), U"iris");
 		collection -> addItem_move (ap.move());
diff --git a/FFNet/FFNet_Activation_Categories.cpp b/FFNet/FFNet_ActivationList_Categories.cpp
similarity index 86%
rename from FFNet/FFNet_Activation_Categories.cpp
rename to FFNet/FFNet_ActivationList_Categories.cpp
index 7175678..1a5a457 100644
--- a/FFNet/FFNet_Activation_Categories.cpp
+++ b/FFNet/FFNet_ActivationList_Categories.cpp
@@ -1,4 +1,4 @@
-/* FFNet_Activation_Categories.cpp
+/* FFNet_ActivationList_Categories.cpp
  *
  * Copyright (C) 1997-2011, 2015-2016 David Weenink
  *
@@ -23,7 +23,7 @@
  djmw 20071014 Melder_error<n>
 */
 
-#include "FFNet_Activation_Categories.h"
+#include "FFNet_ActivationList_Categories.h"
 
 static long winnerTakesAll (FFNet me, const double activation[]) {
 	long pos = 1;
@@ -52,7 +52,7 @@ static long stochastic (FFNet me, const double activation[]) {
 	return i;
 }
 
-autoCategories FFNet_Activation_to_Categories (FFNet me, Activation activation, int labeling) {
+autoCategories FFNet_ActivationList_to_Categories (FFNet me, ActivationList activation, int labeling) {
 	try {
 		long (*labelingFunction) (FFNet me, const double act[]);
 
@@ -75,7 +75,7 @@ autoCategories FFNet_Activation_to_Categories (FFNet me, Activation activation,
 	}
 }
 
-autoActivation FFNet_Categories_to_Activation (FFNet me, Categories thee) {
+autoActivationList FFNet_Categories_to_ActivationList (FFNet me, Categories thee) {
 	try {
 		autoCategories uniq = Categories_selectUniqueItems (thee);
 
@@ -87,7 +87,7 @@ autoActivation FFNet_Categories_to_Activation (FFNet me, Categories thee) {
 			Melder_throw (U"The Categories do not match the categories of the FFNet.");
 		}
 
-		autoActivation him = Activation_create (thy size, my nOutputs);
+		autoActivationList him = ActivationList_create (thy size, my nOutputs);
 		for (long i = 1; i <= thy size; i ++) {
 			const char32 *citem = OrderedOfString_itemAtIndex_c (thee, i);
 			long pos = OrderedOfString_indexOfItem_c (my outputCategories.get(), citem);
@@ -98,8 +98,8 @@ autoActivation FFNet_Categories_to_Activation (FFNet me, Categories thee) {
 		}
 		return him;
 	} catch (MelderError) {
-		Melder_throw (me, U": no Activation created.");
+		Melder_throw (me, U": no ActivationList created.");
 	}
 }
 
-/* End of file FFNet_Activation_Categories.cpp */
+/* End of file FFNet_ActivationList_Categories.cpp */
diff --git a/FFNet/FFNet_Activation_Categories.h b/FFNet/FFNet_ActivationList_Categories.h
similarity index 67%
rename from FFNet/FFNet_Activation_Categories.h
rename to FFNet/FFNet_ActivationList_Categories.h
index 43ad7ce..efe339c 100644
--- a/FFNet/FFNet_Activation_Categories.h
+++ b/FFNet/FFNet_ActivationList_Categories.h
@@ -1,8 +1,8 @@
-#ifndef _FFNet_Activation_Categories_h_
-#define _FFNet_Activation_Categories_h_
-/* FFNet_Activation_Categories.h
+#ifndef _FFNet_ActivationList_Categories_h_
+#define _FFNet_ActivationList_Categories_h_
+/* FFNet_ActivationList_Categories.h
  *
- * Copyright (C) 1997-2011, 2015 David Weenink
+ * Copyright (C) 1997-2011, 2015-2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,14 +25,14 @@
 */
 
 #include "FFNet.h"
-#include "Activation.h"
+#include "ActivationList.h"
 #include "Categories.h"
 
-autoCategories FFNet_Activation_to_Categories (FFNet me, Activation activation, int labeling);
+autoCategories FFNet_ActivationList_to_Categories (FFNet me, ActivationList activation, int labeling);
 /* labeling = 1 : winner-takes-all */
 /* labeling = 2 : stochastic */
 
-autoActivation FFNet_Categories_to_Activation (FFNet me, Categories labels);
+autoActivationList FFNet_Categories_to_ActivationList (FFNet me, Categories labels);
 /* Postcondition: my outputCategories != nullptr; */
 
-#endif /* _FFNet_Activation_Categories_h_ */
+#endif /* _FFNet_ActivationList_Categories_h_ */
diff --git a/FFNet/FFNet_Pattern.cpp b/FFNet/FFNet_PatternList.cpp
similarity index 77%
rename from FFNet/FFNet_Pattern.cpp
rename to FFNet/FFNet_PatternList.cpp
index 53b5d8f..c33ff6d 100644
--- a/FFNet/FFNet_Pattern.cpp
+++ b/FFNet/FFNet_PatternList.cpp
@@ -1,6 +1,6 @@
-/* FFNet_Pattern.cpp
+/* FFNet_PatternList.cpp
  *
- * Copyright (C) 1997-2011 David Weenink
+ * Copyright (C) 1997-2011, 2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,9 +21,9 @@
  djmw 20020712 GPL header
 */
 
-#include "FFNet_Pattern.h"
+#include "FFNet_PatternList.h"
 
-void FFNet_Pattern_drawActivation (FFNet me, Pattern pattern, Graphics g, long index) {
+void FFNet_PatternList_drawActivation (FFNet me, PatternList pattern, Graphics g, long index) {
 	if (index < 1 || index > pattern->ny) {
 		return;
 	}
@@ -31,4 +31,4 @@ void FFNet_Pattern_drawActivation (FFNet me, Pattern pattern, Graphics g, long i
 	FFNet_drawActivation (me, g);
 }
 
-/* End of file FFNet_Pattern.cpp */
+/* End of file FFNet_PatternList.cpp */
diff --git a/FFNet/FFNet_Pattern.h b/FFNet/FFNet_PatternList.h
similarity index 76%
rename from FFNet/FFNet_Pattern.h
rename to FFNet/FFNet_PatternList.h
index 0f5fd42..519c713 100644
--- a/FFNet/FFNet_Pattern.h
+++ b/FFNet/FFNet_PatternList.h
@@ -1,6 +1,6 @@
-#ifndef _FFNet_Pattern_h_
-#define _FFNet_Pattern_h_
-/* FFNet_Pattern.h
+#ifndef _FFNet_PatternList_h_
+#define _FFNet_PatternList_h_
+/* FFNet_PatternList.h
  *
  * Copyright (C) 1997-2011, 2015 David Weenink
  *
@@ -24,9 +24,9 @@
  djmw 20110307 Latest modification
 */
 
-#include "Pattern.h"
+#include "PatternList.h"
 #include "FFNet.h"
 
-void FFNet_Pattern_drawActivation( FFNet me, Pattern pattern, Graphics g, long ipattern );
+void FFNet_PatternList_drawActivation( FFNet me, PatternList pattern, Graphics g, long ipattern );
 
-#endif /* _FFNet_Pattern_h_ */
+#endif /* _FFNet_PatternList_h_ */
diff --git a/FFNet/FFNet_Pattern_Activation.cpp b/FFNet/FFNet_PatternList_ActivationList.cpp
similarity index 61%
rename from FFNet/FFNet_Pattern_Activation.cpp
rename to FFNet/FFNet_PatternList_ActivationList.cpp
index 0c7d02f..71c943e 100644
--- a/FFNet/FFNet_Pattern_Activation.cpp
+++ b/FFNet/FFNet_PatternList_ActivationList.cpp
@@ -1,4 +1,4 @@
-/* FFNet_Pattern_Activation.cpp
+/* FFNet_PatternList_ActivationList.cpp
  *
  * Copyright (C) 1994-2011,2015-2016 David Weenink, 2015 Paul Boersma
  *
@@ -21,11 +21,11 @@
  djmw 20020712 GPL header
  djmw 20030701 Removed non-GPL minimizations
  djmw 20040416 More precise error messages.
- djmw 20041118 Added FFNet_Pattern_Categories_getCosts.
+ djmw 20041118 Added FFNet_PatternList_Categories_getCosts.
 */
 
 #include "Graphics.h"
-#include "FFNet_Pattern_Activation.h"
+#include "FFNet_PatternList_ActivationList.h"
 
 static double func (Daata object, const double p[]) {
 	FFNet me = (FFNet) object;
@@ -63,26 +63,26 @@ static void dfunc_optimized (Daata object, const double p[], double dp[]) {
 	}
 }
 
-static void _FFNet_Pattern_Activation_checkDimensions (FFNet me, Pattern p, Activation a) {
+static void _FFNet_PatternList_ActivationList_checkDimensions (FFNet me, PatternList p, ActivationList a) {
 	if (my nInputs != p -> nx) {
-		Melder_throw (U"The Pattern and the FFNet do not match.\nThe number of columns in the Pattern must equal the number of inputs in the FFNet.");
+		Melder_throw (U"The PatternList and the FFNet do not match.\nThe number of columns in the PatternList must equal the number of inputs in the FFNet.");
 	}
 	if (my nOutputs != a -> nx) {
 		Melder_throw (U"The Activation and the FFNet do not match.\nThe number of columns in the Activation must equal the number of outputs in the FFNet.");
 	}
 	if (p -> ny != a -> ny) {
-		Melder_throw (U"The Pattern and the Activation do not match.\nThe number of rows in the Pattern must equal the number of rows in the Activation.");
+		Melder_throw (U"The PatternList and the ActivationList do not match.\nThe number of rows in the PatternList must equal the number of rows in the Activation.");
 	}
-	if (! _Pattern_checkElements (p)) {
-		Melder_throw (U"All Pattern elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the Pattern values first.");	}
-	if (! _Activation_checkElements (a)) {
+	if (! _PatternList_checkElements (p)) {
+		Melder_throw (U"All PatternList elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the PatternList values first.");	}
+	if (! _ActivationList_checkElements (a)) {
 		Melder_throw (U"All Activation elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the Activation values first.");
 	}
 }
 
-static void _FFNet_Pattern_Activation_learn (FFNet me, Pattern pattern, Activation activation, long maxNumOfEpochs, double tolerance, int costFunctionType, bool reset) {
+static void _FFNet_PatternList_ActivationList_learn (FFNet me, PatternList pattern, ActivationList activation, long maxNumOfEpochs, double tolerance, int costFunctionType, bool reset) {
 	try {
-		_FFNet_Pattern_Activation_checkDimensions (me, pattern, activation);
+		_FFNet_PatternList_ActivationList_checkDimensions (me, pattern, activation);
 
 		// Link the things to be learned
 
@@ -117,7 +117,7 @@ static void _FFNet_Pattern_Activation_learn (FFNet me, Pattern pattern, Activati
 }
 
 
-void FFNet_Pattern_Activation_learnSD (FFNet me, Pattern p, Activation a, long maxNumOfEpochs, double tolerance, double learningRate, double momentum, int costFunctionType) {
+void FFNet_PatternList_ActivationList_learnSD (FFNet me, PatternList p, ActivationList a, long maxNumOfEpochs, double tolerance, double learningRate, double momentum, int costFunctionType) {
 	int resetMinimizer = 0;
 	/* Did we choose another minimizer */
 	if (my minimizer && ! Thing_isa (my minimizer.get(), classSteepestDescentMinimizer)) {
@@ -131,10 +131,10 @@ void FFNet_Pattern_Activation_learnSD (FFNet me, Pattern p, Activation a, long m
 	}
 	((SteepestDescentMinimizer) my minimizer.get()) -> eta = learningRate;
 	((SteepestDescentMinimizer) my minimizer.get()) -> momentum = momentum;
-	_FFNet_Pattern_Activation_learn (me, p, a, maxNumOfEpochs, tolerance, costFunctionType, resetMinimizer);
+	_FFNet_PatternList_ActivationList_learn (me, p, a, maxNumOfEpochs, tolerance, costFunctionType, resetMinimizer);
 }
 
-void FFNet_Pattern_Activation_learnSM (FFNet me, Pattern p, Activation a, long maxNumOfEpochs, double tolerance, int costFunctionType) {
+void FFNet_PatternList_ActivationList_learnSM (FFNet me, PatternList p, ActivationList a, long maxNumOfEpochs, double tolerance, int costFunctionType) {
 	int resetMinimizer = 0;
 
 	// Did we choose another minimizer
@@ -148,12 +148,12 @@ void FFNet_Pattern_Activation_learnSM (FFNet me, Pattern p, Activation a, long m
 		resetMinimizer = 1;
 		my minimizer = VDSmagtMinimizer_create (my dimension, me, func, dfunc_optimized);
 	}
-	_FFNet_Pattern_Activation_learn (me, p, a, maxNumOfEpochs, tolerance, costFunctionType, resetMinimizer);
+	_FFNet_PatternList_ActivationList_learn (me, p, a, maxNumOfEpochs, tolerance, costFunctionType, resetMinimizer);
 }
 
-double FFNet_Pattern_Activation_getCosts_total (FFNet me, Pattern p, Activation a, int costFunctionType) {
+double FFNet_PatternList_ActivationList_getCosts_total (FFNet me, PatternList p, ActivationList a, int costFunctionType) {
 	try {
-		_FFNet_Pattern_Activation_checkDimensions (me, p, a);
+		_FFNet_PatternList_ActivationList_checkDimensions (me, p, a);
 		FFNet_setCostFunction (me, costFunctionType);
 
 		double cost = 0.0;
@@ -167,32 +167,32 @@ double FFNet_Pattern_Activation_getCosts_total (FFNet me, Pattern p, Activation
 	}
 }
 
-double FFNet_Pattern_Activation_getCosts_average (FFNet me, Pattern p, Activation a, int costFunctionType) {
-	double costs = FFNet_Pattern_Activation_getCosts_total (me, p, a, costFunctionType);
+double FFNet_PatternList_ActivationList_getCosts_average (FFNet me, PatternList p, ActivationList a, int costFunctionType) {
+	double costs = FFNet_PatternList_ActivationList_getCosts_total (me, p, a, costFunctionType);
 	return costs == NUMundefined ? NUMundefined : costs / p -> ny;
 }
 
-autoActivation FFNet_Pattern_to_Activation (FFNet me, Pattern p, long layer) {
+autoActivationList FFNet_PatternList_to_ActivationList (FFNet me, PatternList p, long layer) {
 	try {
 		if (layer < 1 || layer > my nLayers) {
 			layer = my nLayers;
 		}
 		if (my nInputs != p -> nx) {
-			Melder_throw (U"The Pattern and the FFNet do not match. The number of colums in the Pattern (", p -> nx, U") should equal the number of inputs in the FFNet (", my nInputs, U").");
+			Melder_throw (U"The PatternList and the FFNet do not match. The number of colums in the PatternList (", p -> nx, U") should equal the number of inputs in the FFNet (", my nInputs, U").");
 		}
-		if (! _Pattern_checkElements (p)) {
-			Melder_throw (U"All Pattern elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the Pattern values first.");
+		if (! _PatternList_checkElements (p)) {
+			Melder_throw (U"All PatternList elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the PatternList values first.");
 		}
 		long nPatterns = p -> ny;
-		autoActivation thee = Activation_create (nPatterns, my nUnitsInLayer[layer]);
+		autoActivationList thee = ActivationList_create (nPatterns, my nUnitsInLayer[layer]);
 
 		for (long i = 1; i <= nPatterns; i++) {
 			FFNet_propagateToLayer (me, p -> z[i], thy z[i], layer);
 		}
 		return thee;
 	} catch (MelderError) {
-		Melder_throw (me, U": no Activation created.");
+		Melder_throw (me, U": no ActivationList created.");
 	}
 }
 
-/* End of file FFNet_Pattern_Activation.cpp */
+/* End of file FFNet_PatternList_ActivationList.cpp */
diff --git a/FFNet/FFNet_Pattern_Activation.h b/FFNet/FFNet_PatternList_ActivationList.h
similarity index 55%
rename from FFNet/FFNet_Pattern_Activation.h
rename to FFNet/FFNet_PatternList_ActivationList.h
index 664242e..4d5a7ca 100644
--- a/FFNet/FFNet_Pattern_Activation.h
+++ b/FFNet/FFNet_PatternList_ActivationList.h
@@ -1,8 +1,8 @@
-#ifndef _FFNet_Pattern_Activation_h_
-#define _FFNet_Pattern_Activation_h_
-/* FFNet_Pattern_Activation.h
+#ifndef _FFNet_PatternList_ActivationList_h_
+#define _FFNet_PatternList_ActivationList_h_
+/* FFNet_PatternList_ActivationList.h
  *
- * Copyright (C) 1994-2011,2015 David Weenink, 2015 Paul Boersma
+ * Copyright (C) 1994-2011,2015-2016 David Weenink, 2015 Paul Boersma
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,22 +27,22 @@
 
 
 #include "FFNet.h"
-#include "Pattern.h"
-#include "Activation.h"
+#include "PatternList.h"
+#include "ActivationList.h"
 #include "Minimizers.h"
 
-void FFNet_Pattern_Activation_learnSD (FFNet me, Pattern p, Activation a, long maxNumOfEpochs,
+void FFNet_PatternList_ActivationList_learnSD (FFNet me, PatternList p, ActivationList a, long maxNumOfEpochs,
     double tolerance, double learningRate, double momentum, int costFunctionType);
 /* Steepest Descent minimization */
 
-void FFNet_Pattern_Activation_learnSM (FFNet me, Pattern p, Activation a, long maxNumOfEpochs,
+void FFNet_PatternList_ActivationList_learnSM (FFNet me, PatternList p, ActivationList a, long maxNumOfEpochs,
     double tolerance, int costFunctionType);
 
-double FFNet_Pattern_Activation_getCosts_total (FFNet me, Pattern p, Activation a, int costFunctionType);
-double FFNet_Pattern_Activation_getCosts_average (FFNet me, Pattern p, Activation a, int costFunctionType);
+double FFNet_PatternList_ActivationList_getCosts_total (FFNet me, PatternList p, ActivationList a, int costFunctionType);
+double FFNet_PatternList_ActivationList_getCosts_average (FFNet me, PatternList p, ActivationList a, int costFunctionType);
 
-autoActivation FFNet_Pattern_to_Activation (FFNet me, Pattern p, long layer);
+autoActivationList FFNet_PatternList_to_ActivationList (FFNet me, PatternList p, long layer);
 /* Calculate the activations at a layer */
 /* if (layer<1 || layer > my nLayers) layer = my nLayers; */
 
-#endif /* _FFNet_Pattern_Activation_h_ */
+#endif /* _FFNet_PatternList_ActivationList_h_ */
diff --git a/FFNet/FFNet_PatternList_Categories.cpp b/FFNet/FFNet_PatternList_Categories.cpp
new file mode 100644
index 0000000..a36bde0
--- /dev/null
+++ b/FFNet/FFNet_PatternList_Categories.cpp
@@ -0,0 +1,99 @@
+/* FFNet_PatternList_Categories.cpp
+ *
+ * Copyright (C) 1994-2011, 2015-2016 David Weenink
+ *
+ * This code 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 code 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 work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ djmw 20020712 GPL header.
+ djmw 20020910 changes.
+ djmw 20030701 Removed non-GPL minimizations.
+ djmw 20041118 Added FFNet_PatternList_Categories_getCosts.
+*/
+
+#include "FFNet_ActivationList_Categories.h"
+#include "FFNet_PatternList_Categories.h"
+#include "FFNet_PatternList_ActivationList.h"
+
+static void _FFNet_PatternList_Categories_checkDimensions (FFNet me, PatternList p, Categories c) {
+	if (my nInputs != p -> nx) {
+		Melder_throw (U"The PatternList and the FFNet do not match.\nThe number of colums in the PatternList must equal the number of inputs in the FFNet.");
+	}
+	if (p -> ny != c->size) {
+		Melder_throw (U"The PatternList and the categories do not match.\nThe number of rows in the PatternList must equal the number of categories.");
+	}
+	if (! _PatternList_checkElements (p)) {
+		Melder_throw (U"All PatternList elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the PatternList values first.");
+	}
+}
+
+double FFNet_PatternList_Categories_getCosts_total (FFNet me, PatternList p, Categories c, int costFunctionType) {
+	try {
+		      _FFNet_PatternList_Categories_checkDimensions (me, p, c);
+		autoActivationList activation = FFNet_Categories_to_ActivationList (me, c);
+		return FFNet_PatternList_ActivationList_getCosts_total (me, p, activation.get(), costFunctionType);
+	} catch (MelderError) {
+		return NUMundefined;
+	}
+}
+
+double FFNet_PatternList_Categories_getCosts_average (FFNet me, PatternList p, Categories c, int costFunctionType) {
+	double costs = FFNet_PatternList_Categories_getCosts_total (me, p, c, costFunctionType);
+	return costs == NUMundefined ? NUMundefined : costs / p -> ny;
+}
+
+void FFNet_PatternList_Categories_learnSD (FFNet me, PatternList p, Categories c, long maxNumOfEpochs, double tolerance, double learningRate, double momentum, int costFunctionType) {
+	   _FFNet_PatternList_Categories_checkDimensions (me, p, c);
+	autoActivationList activation = FFNet_Categories_to_ActivationList (me, c);
+	double min, max;
+	Matrix_getWindowExtrema (p, 0, 0, 0, 0, & min, & max);
+	FFNet_PatternList_ActivationList_learnSD (me, p, activation.get(), maxNumOfEpochs, tolerance, learningRate, momentum, costFunctionType);
+}
+
+void FFNet_PatternList_Categories_learnSM (FFNet me, PatternList p, Categories c, long maxNumOfEpochs, double tolerance, int costFunctionType) {
+	   _FFNet_PatternList_Categories_checkDimensions (me, p, c);
+	autoActivationList activation = FFNet_Categories_to_ActivationList (me, c);
+	double min, max;
+	Matrix_getWindowExtrema (p, 0, 0, 0, 0, & min, & max);
+	FFNet_PatternList_ActivationList_learnSM (me, p, activation.get(), maxNumOfEpochs, tolerance, costFunctionType);
+}
+
+autoCategories FFNet_PatternList_to_Categories (FFNet me, PatternList thee, int labeling) {
+	try {
+		if (! my outputCategories) {
+			Melder_throw (U"The FFNet has no output categories.");
+		}
+		if (my nInputs != thy nx) {
+			Melder_throw (U"The number of colums in the PatternList (", thy nx, U") should equal the number of inputs in the FFNet (", my nInputs, U").");
+		}
+		if (! _PatternList_checkElements (thee)) {
+			Melder_throw (U"All PatternList elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the PatternList values first.");
+		}
+
+		autoCategories him = Categories_create ();
+
+		for (long k = 1; k <= thy ny; k ++) {
+			FFNet_propagate (me, thy z [k], nullptr);
+			long index = FFNet_getWinningUnit (me, labeling);
+			autoSimpleString item = Data_copy (my outputCategories->at [index]);
+			his addItem_move (item.move());
+		}
+		return him;
+	} catch (MelderError) {
+		Melder_throw (me, U": no Categories created.");
+	}
+}
+
+/* End of file FFNet_PatternList_Categories.cpp */
diff --git a/FFNet/FFNet_Pattern_Categories.h b/FFNet/FFNet_PatternList_Categories.h
similarity index 57%
rename from FFNet/FFNet_Pattern_Categories.h
rename to FFNet/FFNet_PatternList_Categories.h
index 4684a6b..99dafd9 100644
--- a/FFNet/FFNet_Pattern_Categories.h
+++ b/FFNet/FFNet_PatternList_Categories.h
@@ -1,8 +1,8 @@
-#ifndef _FFNet_Pattern_Categories_h_
-#define _FFNet_Pattern_Categories_h_
-/* FFNet_Pattern_Categories.h
+#ifndef _FFNet_PatternList_Categories_h_
+#define _FFNet_PatternList_Categories_h_
+/* FFNet_PatternList_Categories.h
  *
- * Copyright (C) 1994-2011, 2015 David Weenink
+ * Copyright (C) 1994-2011, 2015-2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,25 +25,25 @@
 */
 
 #include "FFNet.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "Categories.h"
 #include "Minimizers.h"
 
-void FFNet_Pattern_Categories_learnSD (FFNet me, Pattern p, Categories c, long maxNumOfEpochs,
+void FFNet_PatternList_Categories_learnSD (FFNet me, PatternList p, Categories c, long maxNumOfEpochs,
     double tolerance, double learningRate, double momentum, int costFunctionType);
 /* Steepest descent */
 
-void FFNet_Pattern_Categories_learnSM (FFNet me, Pattern p, Categories c, long maxNumOfEpochs,
+void FFNet_PatternList_Categories_learnSM (FFNet me, PatternList p, Categories c, long maxNumOfEpochs,
     double tolerance, int costFunctionType);
 /* Conj. Gradient vdSmagt */
 
-double FFNet_Pattern_Categories_getCosts_total (FFNet me, Pattern p, Categories c, int costFunctionType);
-double FFNet_Pattern_Categories_getCosts_average (FFNet me, Pattern p, Categories c, int costFunctionType);
+double FFNet_PatternList_Categories_getCosts_total (FFNet me, PatternList p, Categories c, int costFunctionType);
+double FFNet_PatternList_Categories_getCosts_average (FFNet me, PatternList p, Categories c, int costFunctionType);
 
-autoCategories FFNet_Pattern_to_Categories (FFNet me, Pattern p, int labeling);
-/* classify the Pattern */
+autoCategories FFNet_PatternList_to_Categories (FFNet me, PatternList p, int labeling);
+/* classify the PatternList */
 /* labeling = 1 : winner-takes-all */
 /* labeling = 2 : stochastic */
 /* Preconditions: I have labels */
 
-#endif /* _FFNet_Pattern_Categories_h_ */
+#endif /* _FFNet_PatternList_Categories_h_ */
diff --git a/FFNet/FFNet_Pattern_Categories.cpp b/FFNet/FFNet_Pattern_Categories.cpp
deleted file mode 100644
index e88f9f1..0000000
--- a/FFNet/FFNet_Pattern_Categories.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/* FFNet_Pattern_Categories.cpp
- *
- * Copyright (C) 1994-2011, 2015 David Weenink
- *
- * This code 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 code 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 work. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/*
- djmw 20020712 GPL header.
- djmw 20020910 changes.
- djmw 20030701 Removed non-GPL minimizations.
- djmw 20041118 Added FFNet_Pattern_Categories_getCosts.
-*/
-
-#include "FFNet_Activation_Categories.h"
-#include "FFNet_Pattern_Categories.h"
-#include "FFNet_Pattern_Activation.h"
-
-static void _FFNet_Pattern_Categories_checkDimensions (FFNet me, Pattern p, Categories c) {
-	if (my nInputs != p -> nx) {
-		Melder_throw (U"The Pattern and the FFNet do not match.\nThe number of colums in the Pattern must equal the number of inputs in the FFNet.");
-	}
-	if (p -> ny != c->size) {
-		Melder_throw (U"The Pattern and the categories do not match.\nThe number of rows in the Pattern must equal the number of categories.");
-	}
-	if (! _Pattern_checkElements (p)) {
-		Melder_throw (U"All Pattern elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the Pattern values first.");
-	}
-}
-
-double FFNet_Pattern_Categories_getCosts_total (FFNet me, Pattern p, Categories c, int costFunctionType) {
-	try {
-		_FFNet_Pattern_Categories_checkDimensions (me, p, c);
-		autoActivation activation = FFNet_Categories_to_Activation (me, c);
-		return FFNet_Pattern_Activation_getCosts_total (me, p, activation.get(), costFunctionType);
-	} catch (MelderError) {
-		return NUMundefined;
-	}
-}
-
-double FFNet_Pattern_Categories_getCosts_average (FFNet me, Pattern p, Categories c, int costFunctionType) {
-	double costs = FFNet_Pattern_Categories_getCosts_total (me, p, c, costFunctionType);
-	return costs == NUMundefined ? NUMundefined : costs / p -> ny;
-}
-
-void FFNet_Pattern_Categories_learnSD (FFNet me, Pattern p, Categories c, long maxNumOfEpochs, double tolerance, double learningRate, double momentum, int costFunctionType) {
-	_FFNet_Pattern_Categories_checkDimensions (me, p, c);
-	autoActivation activation = FFNet_Categories_to_Activation (me, c);
-	double min, max;
-	Matrix_getWindowExtrema (p, 0, 0, 0, 0, & min, & max);
-	FFNet_Pattern_Activation_learnSD (me, p, activation.get(), maxNumOfEpochs, tolerance, learningRate, momentum, costFunctionType);
-}
-
-void FFNet_Pattern_Categories_learnSM (FFNet me, Pattern p, Categories c, long maxNumOfEpochs, double tolerance, int costFunctionType) {
-	_FFNet_Pattern_Categories_checkDimensions (me, p, c);
-	autoActivation activation = FFNet_Categories_to_Activation (me, c);
-	double min, max;
-	Matrix_getWindowExtrema (p, 0, 0, 0, 0, & min, & max);
-	FFNet_Pattern_Activation_learnSM (me, p, activation.get(), maxNumOfEpochs, tolerance, costFunctionType);
-}
-
-autoCategories FFNet_Pattern_to_Categories (FFNet me, Pattern thee, int labeling) {
-	try {
-		if (! my outputCategories) {
-			Melder_throw (U"The FFNet has no output categories.");
-		}
-		if (my nInputs != thy nx) {
-			Melder_throw (U"The number of colums in the Pattern (", thy nx, U") should equal the number of inputs in the FFNet (", my nInputs, U").");
-		}
-		if (! _Pattern_checkElements (thee)) {
-			Melder_throw (U"All Pattern elements must be in the interval [0, 1].\nYou could use \"Formula...\" to scale the Pattern values first.");
-		}
-
-		autoCategories him = Categories_create ();
-
-		for (long k = 1; k <= thy ny; k ++) {
-			FFNet_propagate (me, thy z [k], nullptr);
-			long index = FFNet_getWinningUnit (me, labeling);
-			autoSimpleString item = Data_copy (my outputCategories->at [index]);
-			his addItem_move (item.move());
-		}
-		return him;
-	} catch (MelderError) {
-		Melder_throw (me, U": no Categories created.");
-	}
-}
-
-/* End of file FFNet_Pattern_Categories.cpp */
diff --git a/FFNet/Makefile b/FFNet/Makefile
index b8d4684..a6cfcdf 100644
--- a/FFNet/Makefile
+++ b/FFNet/Makefile
@@ -1,14 +1,14 @@
 # Makefile of the library "FFNet"
-# David Weenink, 22 February 2010
+# David Weenink, 24 May 2016
 
 include ../makefile.defs
 
-CPPFLAGS = -I ../num -I ../dwtools -I ../fon -I ../sys -I ../dwsys -I ../stat
+CPPFLAGS = -I ../num -I ../dwtools -I ../gram -I ../fon -I ../sys -I ../dwsys -I ../stat
 
 OBJECTS = FFNet.o \
-	FFNet_Eigen.o FFNet_Matrix.o FFNet_Pattern.o \
-	FFNet_Activation_Categories.o FFNet_Pattern_Activation.o \
-	FFNet_Pattern_Categories.o \
+	FFNet_Eigen.o FFNet_Matrix.o FFNet_PatternList.o \
+	FFNet_ActivationList_Categories.o FFNet_PatternList_ActivationList.o \
+	FFNet_PatternList_Categories.o RBM_extensions.o \
 	praat_FFNet_init.o manual_FFNet.o
 
 .PHONY: all clean
diff --git a/FFNet/RBM_extensions.cpp b/FFNet/RBM_extensions.cpp
new file mode 100644
index 0000000..e10425b
--- /dev/null
+++ b/FFNet/RBM_extensions.cpp
@@ -0,0 +1,38 @@
+/* RBM_extensions.cpp
+ *
+ * Copyright (C) 2016 David Weenink
+ *
+ * This code 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 code 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 work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "RBM_extensions.h"
+#include "NUM2.h"
+
+autoActivationList RBM_PatternList_to_ActivationList (RBM me, PatternList thee) {
+	try {
+		autoActivationList activations = ActivationList_create (thy ny, my numberOfOutputNodes);
+		for (long ipattern = 1; ipattern <= thy ny; ipattern ++) {
+			RBM_PatternList_applyToInput (me, thee, ipattern);
+			RBM_spreadUp (me);
+			RBM_sampleOutput (me);
+			NUMvector_copyElements <double> (my outputActivities, activations -> z [ipattern], 1, my numberOfOutputNodes);
+		}
+		return activations;
+	} catch (MelderError) {
+		Melder_throw (me, thee, U"No ActivationList created.");
+	}
+}
+
+/* End of file RBM_extensions.cpp */
+
diff --git a/contrib/ola/KNN_def.h b/FFNet/RBM_extensions.h
similarity index 53%
copy from contrib/ola/KNN_def.h
copy to FFNet/RBM_extensions.h
index 2187052..e8a7d9f 100644
--- a/contrib/ola/KNN_def.h
+++ b/FFNet/RBM_extensions.h
@@ -1,6 +1,8 @@
-/* KNN_def.h
+#ifndef _RBM_extensions_h_
+#define _RBM_extensions_h_
+/* RBM_extensions.h
  *
- * Copyright (C) 2007-2009 Ola Söder, 2011,2015 Paul Boersma
+ * Copyright (C) 2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -9,28 +11,18 @@
  *
  * This code 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.
+ * 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 work. If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "ActivationList.h"
+#include "PatternList.h"
+#include "RBM.h"
 
-#define ooSTRUCT KNN
-oo_DEFINE_CLASS (KNN, Daata)
 
-	oo_LONG (nInstances)
-	oo_AUTO_OBJECT (Pattern, 2, input)
-	oo_AUTO_OBJECT (Categories, 0, output)
+autoActivationList RBM_PatternList_to_ActivationList (RBM me, PatternList thee);
 
-	#if oo_DECLARING
-		void v_info ()
-			override;
-	#endif
-
-oo_END_CLASS (KNN)
-#undef ooSTRUCT
-
-
-/* End of file KNN_def.h */
+#endif /* _RBM_extensions_h_ */
diff --git a/FFNet/manual_FFNet.cpp b/FFNet/manual_FFNet.cpp
index 7db8f76..079e345 100644
--- a/FFNet/manual_FFNet.cpp
+++ b/FFNet/manual_FFNet.cpp
@@ -101,12 +101,12 @@ NORMAL (U"The hope is that eventually, probably after many epochs, "
 	"the neural net will come to remember these pattern-category pairs. "
 	"You even hope that the neural net, when the learning phase has terminated, will be able to %generalize "
 	"and has learned to "
-	"@@FFNet & Pattern: To Categories...|classify@ correctly any unknown pattern presented to it. ")
+	"@@FFNet & PatternList: To Categories...|classify@ correctly any unknown pattern presented to it. ")
 NORMAL (U"Because real-life data often contains noise as well as partly contradictory information, "
 	"these hopes can be fulfilled only partly. ")
-NORMAL (U"For @@FFNet & Pattern & Categories: Learn...|learning@ you "
+NORMAL (U"For @@FFNet & PatternList & Categories: Learn...|learning@ you "
 	"need to select three different objects together: a FFNet (the %classifier), "
-	"a Pattern (the %inputs) and a Categories (the %%correct outputs%).")
+	"a PatternList (the %inputs) and a Categories (the %%correct outputs%).")
 ENTRY (U"How long will the learning phase take?")
 NORMAL (U"In general, this question is hard to answer. It depends on the size of the neural network, "
 	"the number of patterns to be learned, the number of epochs, the tolerance of the minimizer "
@@ -120,8 +120,8 @@ INTRO (U"In the classification phase, the weights of the network are fixed. ")
 NORMAL (U"A pattern, presented at the inputs, will be transformed from layer to layer until it reaches the output layer. "
 	"Now classification can occur by selecting the category associated with the output unit that has "
 	"the largest output value.  "
-	"For classification we only need to select an FFNet and a Pattern together and "
-	"choose @@FFNet & Pattern: To Categories...|To Categories... at . ")
+	"For classification we only need to select an FFNet and a PatternList together and "
+	"choose @@FFNet & PatternList: To Categories...|To Categories... at . ")
 NORMAL (U"In contrast to the @@Feedforward neural networks 1.1. The learning phase|learning phase@ classification is very fast.")
 MAN_END
 
@@ -129,12 +129,12 @@ MAN_BEGIN (U"Feedforward neural networks 2. Quick start", U"djmw", 20040426)
 INTRO (U"You may create the iris example set with the @@Create iris example...@ command "
 	"that you will find under the ##Feedforward neural networks# option in the #New menu. "
 	"Three new objects will appear in the @@List of Objects@: a @FFNet, a @Categories and "
-	"a @Pattern.")
-NORMAL (U"The #Pattern contains the @@iris data set@ in a matrix of 150 rows by 4 columns. "
-	"To guarantee that every cell in the Pattern is in the [0,1] interval, all measurement "
+	"a @PatternList.")
+NORMAL (U"The #PatternList contains the @@iris data set@ in a matrix of 150 rows by 4 columns. "
+	"To guarantee that every cell in the PatternList is in the [0,1] interval, all measurement "
 	"values were divided by 10. In the #Categories the three iris species %setosa, "
 	"%versicolor, and %virginica were categorized with the numbers #1, #2 and #3, respectively. "
-	"Because there are 4 data columns in the Pattern and 3 different iris species in the Categories, "
+	"Because there are 4 data columns in the PatternList and 3 different iris species in the Categories, "
 	"the newly created #FFNet has 4 inputs and 3 outputs. "
 	"If you enter a positive number in one of the fields in the form, the FFNet will have "
 	"this number of units in a %%hidden layer%. The name of the newly created FFNet "
@@ -142,15 +142,15 @@ NORMAL (U"The #Pattern contains the @@iris data set@ in a matrix of 150 rows by
 ENTRY (U"Learning the iris data")
 NORMAL (U"The first thing you probably might want to do is to let the #FFNet learn the association in "
 	"each pattern-category pair. To do this select all three objects together and choose "
-	"@@FFNet & Pattern & Categories: Learn...|Learn... at . "
+	"@@FFNet & PatternList & Categories: Learn...|Learn... at . "
 	"A form will appear, asking you to supply some settings for "
 	"the learning algorithm. Learning starts after you have clicked the OK button. "
 	"As the example network has only a small number of weights that need to be adjusted, "
 	"and the learning data set is very small, this will only take a very short time.")
 ENTRY (U"Classification")
 NORMAL (U"Now, if you are curious how well the FFNet has learned the iris data, you may select the "
-	"#FFNet and the #Pattern together and choose @@FFNet & Pattern: To Categories...|To Categories... at . "
-	"A new #Categories appears in the ##List of Objects# with the name %%4-3_iris% (if %%4-3% was the name of the FFNet and %iris% the name of the Pattern). "
+	"#FFNet and the #PatternList together and choose @@FFNet & PatternList: To Categories...|To Categories... at . "
+	"A new #Categories appears in the ##List of Objects# with the name %%4-3_iris% (if %%4-3% was the name of the FFNet and %iris% the name of the PatternList). "
 	"We have two different Categories in the list of objects, the topmost one has the original categories, the other "
 	"the categories as were assigned by the FFNet classifier. The obvious thing to do now is to compare the "
 	"original categories with the assigned categories by making a @@Confusion|confusion table at . "
@@ -160,7 +160,7 @@ NORMAL (U"Now, if you are curious how well the FFNet has learned the iris data,
 NORMAL (U"You may also want to "
 	"@@Feedforward neural networks 3. FFNet versus discriminant classifier|compare the FFNet classifier with a discriminant classifier at .")
 ENTRY (U"Create other neural net topologies")
-NORMAL (U"With a #Pattern and a #Categories selected together, you can for example create a new #FFNet of a different topology.")
+NORMAL (U"With a #PatternList and a #Categories selected together, you can for example create a new #FFNet of a different topology.")
 MAN_END
 
 MAN_BEGIN (U"Feedforward neural networks 3. FFNet versus discriminant classifier", U"djmw", 20040426)
@@ -168,9 +168,9 @@ NORMAL (U"You may want to compare the FFNet classifier with a discriminant class
 	"Unlike the FFNet, a @@Discriminant|discriminant@ classifier does not need any iterative procedure in the "
 	"learning phase and can be used immediately after creation for classification. "
 	"The following three simple steps will give you the confusion matrix based on discriminant analysis:")
-LIST_ITEM (U"1. Select the Pattern and the Categories together and choose ##To Discriminant#.  "
+LIST_ITEM (U"1. Select the PatternList and the Categories together and choose ##To Discriminant#.  "
 	"A newly created Discriminant will appear.")
-LIST_ITEM (U"2. Select the Discriminant and the Pattern together and choose ##To Categories...#. A newly created @Categories will appear.")
+LIST_ITEM (U"2. Select the Discriminant and the PatternList together and choose ##To Categories...#. A newly created @Categories will appear.")
 LIST_ITEM (U"3. Select the two appropriate Categories and choose @@categories: To Confusion|To Confusion at .  "
 	"A newly created @Confusion will appear. After pushing the @Info button, the info window will "
 	"show you the fraction correct.")
@@ -180,24 +180,30 @@ MAN_END
 MAN_BEGIN (U"Feedforward neural networks 4. Command overview", U"djmw", 20040426)
 INTRO (U"FFNet commands")
 ENTRY (U"Creation:")
-LIST_ITEM (U"\\bu @@Pattern & Categories: To FFNet...@")
+LIST_ITEM (U"\\bu @@PatternList & Categories: To FFNet...@")
 LIST_ITEM (U"\\bu @@Create FFNet...@")
 ENTRY (U"Learning:")
-LIST_ITEM (U"\\bu @@FFNet & Pattern & Categories: Learn...@")
-LIST_ITEM (U"\\bu @@FFNet & Pattern & Categories: Learn slow...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & Categories: Learn...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & Categories: Learn slow...@")
 ENTRY (U"Classification:")
-LIST_ITEM (U"\\bu @@FFNet & Pattern: To Categories...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList: To Categories...@")
 ENTRY (U"Drawing:")
 LIST_ITEM (U"\\bu @@FFNet: Draw topology@")
 LIST_ITEM (U"\\bu @@FFNet: Draw weights...@")
 LIST_ITEM (U"\\bu @@FFNet: Draw cost history...@")
 ENTRY (U"Queries")
-LIST_ITEM (U"\\bu @@FFNet & Pattern & Categories: Get total costs...@")
-LIST_ITEM (U"\\bu @@FFNet & Pattern & Categories: Get average costs...@")
-LIST_ITEM (U"\\bu @@FFNet & Pattern & Activation: Get total costs...@")
-LIST_ITEM (U"\\bu @@FFNet & Pattern & Activation: Get average costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & Categories: Get total costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & Categories: Get average costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & ActivationList: Get total costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & ActivationList: Get average costs...@")
 ENTRY (U"Analysis:")
-LIST_ITEM (U"\\bu ##FFNet & Pattern: To Activation...#")
+LIST_ITEM (U"\\bu ##FFNet & PatternList: To ActivationList...#")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & Categories: Get total costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & Categories: Get average costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & ActivationList: Get total costs...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList & ActivationList: Get average costs...@")
+ENTRY (U"Analysis:")
+LIST_ITEM (U"\\bu ##FFNet & PatternList: To Activation...#")
 ENTRY (U"Modification:")
 LIST_ITEM (U"\\bu @@FFNet: Reset...@")
 LIST_ITEM (U"\\bu ##FFNet: Select biases...#")
@@ -337,9 +343,9 @@ FFNet_Create_COMMON_HELP_INOUT
 FFNet_Create_COMMON_HELP_HIDDEN
 MAN_END
 
-MAN_BEGIN (U"Create iris example...", U"djmw", 20040423)
+MAN_BEGIN (U"Create iris example...", U"djmw", 20160524)
 INTRO (U"A @FFNet feedforward neural net will be created together with two other objects: "
-	"a @Pattern and a @Categories. The Pattern will contain the observations in the @@iris data set@, "
+	"a @PatternList and a @Categories. The PatternList will contain the observations in the @@iris data set@, "
 	"and the Categories will contain the 3 different iris species categorized by numbers.")
 ENTRY (U"Settings")
 FFNet_Create_COMMON_HELP_HIDDEN
@@ -354,58 +360,52 @@ NORMAL (U"A data set with 150 random samples of flowers from the iris species %s
 	"used by @@Fisher (1936)@ in his initiation of the linear-discriminant-function technique.")
 MAN_END
 
-MAN_BEGIN (U"FFNet: Pattern", U"djmw", 19960918)
-INTRO (U"A @Pattern is a @Matrix in which each row forms one input pattern (vector) for the neural net.")
-NORMAL (U"The number of columns is the dimensionality of the input. "
-"The number of rows is the number of patterns.")
-MAN_END
-
 MAN_BEGIN (U"FFNet: Categories", U"djmw", 19960918)
-INTRO (U"The categories for training a neural net with a @Pattern. ")
+INTRO (U"The categories for training a neural net with a @PatternList. ")
 ENTRY (U"Preconditions")
-NORMAL (U"The number of categories in a @Categories must equal the number of rows in #Pattern.")
+NORMAL (U"The number of categories in a @Categories must equal the number of rows in #PatternList.")
 MAN_END
 
-MAN_BEGIN (U"Activation", U"djmw", 20041118)
-INTRO (U"A @Matrix whose elements must be >= 0 and <= 1. "
-"Classification: the response of a particular layer in a neural net to a @Pattern."
-"Learning: the desired response of the output layer in a neural net to a @Pattern.")
+MAN_BEGIN (U"ActivationList", U"djmw", 20160524)
+INTRO (U"A list of activations, organized as a @Matrix whose elements must be >= 0 and <= 1. "
+"Classification: the response of a particular layer in a neural net to a @PatternList."
+"Learning: the desired response of the output layer in a neural net to a @PatternList.")
 MAN_END
 
 MAN_BEGIN (U"FFNet: Principal components", U"djmw", 19960918)
 INTRO (U"When you select @FFNet and @Eigen the decision planes of layer 1 are drawn in the PC-plane.\n")
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern: To Categories...", U"djmw", 19960918)
-INTRO (U"The @FFNet is used as a classifier. Each pattern from the @Pattern will be "
+MAN_BEGIN (U"FFNet & PatternList: To Categories...", U"djmw", 19960918)
+INTRO (U"The @FFNet is used as a classifier. Each pattern from the @PatternList will be "
 	"classified into one of the FFNet's categories.")
 MAN_END
 
-MAN_BEGIN (U"Pattern & Categories: To FFNet...", U"djmw", 20040422)
+MAN_BEGIN (U"PatternList & Categories: To FFNet...", U"djmw", 20040422)
 INTRO (U"Create a new @FFNet feedforward neural network. "
 	"The number of inputs of the newly created FFNet will be equal to the number of "
-	"columns in the @Pattern and the number of outputs "
+	"columns in the @PatternList and the number of outputs "
 	"will be equal to the number of unique categories in the @Categories.")
 ENTRY (U"Settings")
 FFNet_Create_COMMON_HELP_HIDDEN
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern & Categories: Learn slow...", U"djmw", 19960918)
-INTRO (U"To learn an association you have to select a @FFNet, a @Pattern and a @Categories object.")
+MAN_BEGIN (U"FFNet & PatternList & Categories: Learn slow...", U"djmw", 19960918)
+INTRO (U"To learn an association you have to select a @FFNet, a @PatternList and a @Categories object.")
 ENTRY (U"Preconditions")
-LIST_ITEM (U"The number of columns in a #Pattern must equal the number of input units of #FFNet.")
+LIST_ITEM (U"The number of columns in a #PatternList must equal the number of input units of #FFNet.")
 ENTRY (U" Algorithm")
 NORMAL (U"Steepest descent")
 ENTRY (U"Preconditions")
-LIST_ITEM (U"The number of rows in a #Pattern must equal the number of categories in a #Categories.")
+LIST_ITEM (U"The number of rows in a #PatternList must equal the number of categories in a #Categories.")
 LIST_ITEM (U"The number of unique categories in a #Categories must equal the number of output units in #FFNet.")
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern & Categories: Learn...", U"djmw", 20040511)
-INTRO (U"You can choose this command after selecting one @Pattern, one @Categories and one @FFNet.")
+MAN_BEGIN (U"FFNet & PatternList & Categories: Learn...", U"djmw", 20040511)
+INTRO (U"You can choose this command after selecting one @PatternList, one @Categories and one @FFNet.")
 ENTRY (U"Settings")
 TAG (U"##Maximum number of epochs")
-DEFINITION (U"the maximum number of times that the complete #Pattern dataset will be presented to the neural net.")
+DEFINITION (U"the maximum number of times that the complete #PatternList dataset will be presented to the neural net.")
 TAG (U"##Tolerance of minimizer")
 DEFINITION (U"when the difference in costs between two successive learning cycles is "
 "smaller than this value, the minimization process will be stopped.")
@@ -421,29 +421,29 @@ NORMAL (U"The minimization procedure is a variant of conjugate gradient minimiza
 	"see for example @@Press et al. (1992)@, chapter 10, or @@Nocedal & Wright (1999)@, chapter 5.")
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern & Categories: Get total costs...", U"djmw", 20041118)
-INTRO (U"Query the selected @FFNet, @Pattern and @Categories for the total costs.")
+MAN_BEGIN (U"FFNet & PatternList & Categories: Get total costs...", U"djmw", 20041118)
+INTRO (U"Query the selected @FFNet, @PatternList and @Categories for the total costs.")
 ENTRY (U"Algorithm")
-NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & Pattern & Categories: Learn... at . ")
+NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & PatternList & Categories: Learn... at . ")
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern & Activation: Get total costs...", U"djmw", 20041118)
-INTRO (U"Query the selected @FFNet, @Pattern and @Activation for the total costs.")
+MAN_BEGIN (U"FFNet & PatternList & ActivationList: Get total costs...", U"djmw", 20160524)
+INTRO (U"Query the selected @FFNet, @PatternList and @ActivationList for the total costs.")
 ENTRY (U"Algorithm")
-NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & Pattern & Categories: Learn... at . ")
+NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & PatternList & Categories: Learn... at . ")
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern & Categories: Get average costs...", U"djmw", 20041118)
-INTRO (U"Query the selected @FFNet, @Pattern and @Categories for the average costs.")
+MAN_BEGIN (U"FFNet & PatternList & Categories: Get average costs...", U"djmw", 20041118)
+INTRO (U"Query the selected @FFNet, @PatternList and @Categories for the average costs.")
 ENTRY (U"Algorithm")
-NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & Pattern & Categories: Learn... at . "
+NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & PatternList & Categories: Learn... at . "
 	"These total costs are then divided by the number of patterns.")
 MAN_END
 
-MAN_BEGIN (U"FFNet & Pattern & Activation: Get average costs...", U"djmw", 20041118)
-INTRO (U"Query the selected @FFNet, @Pattern and @Activation for the average costs.")
+MAN_BEGIN (U"FFNet & PatternList & ActivationList: Get average costs...", U"djmw", 20160526)
+INTRO (U"Query the selected @FFNet, @PatternList and @ActivationList for the average costs.")
 ENTRY (U"Algorithm")
-NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & Pattern & Categories: Learn... at . "
+NORMAL (U"All patterns are propagated and the total costs are calculated as is shown in @@FFNet & PatternList & Categories: Learn... at . "
 	"These total costs are then divided by the number of patterns.")
 MAN_END
 
diff --git a/FFNet/praat_FFNet_init.cpp b/FFNet/praat_FFNet_init.cpp
index 4a9603d..9c26798 100644
--- a/FFNet/praat_FFNet_init.cpp
+++ b/FFNet/praat_FFNet_init.cpp
@@ -36,10 +36,11 @@
 #include "Minimizers.h"
 #include "FFNet_Eigen.h"
 #include "FFNet_Matrix.h"
-#include "FFNet_Pattern.h"
-#include "FFNet_Activation_Categories.h"
-#include "FFNet_Pattern_Activation.h"
-#include "FFNet_Pattern_Categories.h"
+#include "FFNet_PatternList.h"
+#include "FFNet_ActivationList_Categories.h"
+#include "FFNet_PatternList_ActivationList.h"
+#include "FFNet_PatternList_Categories.h"
+#include "RBM_extensions.h"
 
 /* Routines to be removed sometime in the future:
 20040422, 2.4.04: FFNet_drawWeightsToLayer  use FFNet_drawWeights
@@ -269,15 +270,15 @@ DIRECT (FFNet_getNumberOfOutputWeights)
 	}
 END
 
-/**************** New Pattern ***************************/
+/**************** New PatternList ***************************/
 
-FORM (Pattern_create, U"Create Pattern", nullptr)
+FORM (PatternList_create, U"Create PatternList", nullptr)
 	WORD (U"Name", U"1x1")
 	NATURAL (U"Dimension of a pattern", U"1")
 	NATURAL (U"Number of patterns", U"1")
 	OK
 DO
-	praat_new (Pattern_create (GET_INTEGER (U"Number of patterns"),
+	praat_new (PatternList_create (GET_INTEGER (U"Number of patterns"),
 		GET_INTEGER (U"Dimension of a pattern")), GET_STRING (U"Name"));
 END
 
@@ -412,17 +413,17 @@ DO
 	}
 END
 
-/******************* FFNet && Activation *************************************/
+/******************* FFNet && ActivationList *************************************/
 
-FORM (FFNet_Activation_to_Categories, U"FFNet & Activation: To Categories", 0)
+FORM (FFNet_ActivationList_to_Categories, U"FFNet & ActivationList: To Categories", 0)
 	RADIO (U"Kind of labeling", 1)
 	RADIOBUTTON (U"Winner-takes-all")
 	RADIOBUTTON (U"Stochastic")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Activation thee = FIRST (Activation);
-	autoCategories him = FFNet_Activation_to_Categories (me, thee, GET_INTEGER (U"Kind of labeling"));
+	ActivationList thee = FIRST (ActivationList);
+	autoCategories him = FFNet_ActivationList_to_Categories (me, thee, GET_INTEGER (U"Kind of labeling"));
 	praat_new (him.move(), my name, U"_", thy name);
 END
 
@@ -471,10 +472,10 @@ END
 
 /************************* FFNet && Categories **********************************/
 
-DIRECT (FFNet_Categories_to_Activation)
+DIRECT (FFNet_Categories_to_ActivationList)
 	FFNet me = FIRST (FFNet);
 	Categories thee = FIRST (Categories);
-	autoActivation him = FFNet_Categories_to_Activation (me, thee);
+	autoActivationList him = FFNet_Categories_to_ActivationList (me, thee);
 	praat_new (him.move(), my name);
 END
 
@@ -490,77 +491,77 @@ DO
 	praat_new (him.move(), my name);
 END
 
-/************************* FFNet && Pattern **********************************/
+/************************* FFNet && PatternList **********************************/
 
-FORM (FFNet_Pattern_drawActivation, U"Draw an activation", 0)
-	NATURAL (U"Pattern (row) number", U"1");
+FORM (FFNet_PatternList_drawActivation, U"Draw an activation", 0)
+	NATURAL (U"PatternList (row) number", U"1");
 	OK
 DO
 	autoPraatPicture picture;
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	FFNet_Pattern_drawActivation (me, thee, GRAPHICS, GET_INTEGER (U"Pattern"));
+	PatternList thee = FIRST (PatternList);
+	FFNet_PatternList_drawActivation (me, thee, GRAPHICS, GET_INTEGER (U"PatternList"));
 END
 
-FORM (FFNet_Pattern_to_Activation, U"To activations in layer", 0)
+FORM (FFNet_PatternList_to_ActivationList, U"To activations in layer", 0)
 	NATURAL (U"Layer", U"1")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	autoActivation him = FFNet_Pattern_to_Activation (me, thee, GET_INTEGER (U"Layer"));
+	PatternList thee = FIRST (PatternList);
+	autoActivationList him = FFNet_PatternList_to_ActivationList (me, thee, GET_INTEGER (U"Layer"));
 	praat_new (him.move(), my name, U"_", thy name);
 END
 
-DIRECT (hint_FFNet_and_Pattern_classify)
+DIRECT (hint_FFNet_and_PatternList_classify)
 	Melder_information (U"You can use the FFNet as a classifier by selecting a\n"
-		"FFNet and a Pattern together and choosing \"To Categories...\".");
+		"FFNet and a PatternList together and choosing \"To Categories...\".");
 END
 
-DIRECT (hint_FFNet_and_Pattern_and_Categories_learn)
+DIRECT (hint_FFNet_and_PatternList_and_Categories_learn)
 	Melder_information (U"You can teach a FFNet to classify by selecting a\n"
-		"FFNet, a Pattern and a Categories together and choosing \"Learn...\".");
+		"FFNet, a PatternList and a Categories together and choosing \"Learn...\".");
 END
 
-FORM (FFNet_Pattern_to_Categories, U"FFNet & Pattern: To Categories", U"FFNet & Pattern: To Categories...")
+FORM (FFNet_PatternList_to_Categories, U"FFNet & PatternList: To Categories", U"FFNet & PatternList: To Categories...")
 	RADIO (U"Determine output category as", 1)
 		RADIOBUTTON (U"Winner-takes-all")
 		RADIOBUTTON (U"Stochastic")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	autoCategories him = FFNet_Pattern_to_Categories (me, thee, GET_INTEGER (U"Determine output category as"));
+	PatternList thee = FIRST (PatternList);
+	autoCategories him = FFNet_PatternList_to_Categories (me, thee, GET_INTEGER (U"Determine output category as"));
 	praat_new (him.move(), my name, U"_", thy name);
 END
 
-/*********** FFNet Pattern Activation **********************************/
+/*********** FFNet PatternList ActivationList **********************************/
 
-FORM (FFNet_Pattern_Activation_getCosts_total, U"FFNet & Pattern & Activation: Get total costs", U"FFNet & Pattern & Activation: Get total costs...")
+FORM (FFNet_PatternList_ActivationList_getCosts_total, U"FFNet & PatternList & ActivationList: Get total costs", U"FFNet & PatternList & ActivationList: Get total costs...")
 	RADIO (U"Cost function", 1)
 	RADIOBUTTON (U"Minimum-squared-error")
 	RADIOBUTTON (U"Minimum-cross-entropy")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	Activation him = FIRST (Activation);
-	Melder_information (FFNet_Pattern_Activation_getCosts_total (me, thee, him, GET_INTEGER (U"Cost function")));
+	PatternList thee = FIRST (PatternList);
+	ActivationList him = FIRST (ActivationList);
+	Melder_information (FFNet_PatternList_ActivationList_getCosts_total (me, thee, him, GET_INTEGER (U"Cost function")));
 END
 
-FORM (FFNet_Pattern_Activation_getCosts_average, U"FFNet & Pattern & Activation: Get average costs", U"FFNet & Pattern & Activation: Get average costs...")
+FORM (FFNet_PatternList_ActivationList_getCosts_average, U"FFNet & PatternList & ActivationList: Get average costs", U"FFNet & PatternList & ActivationList: Get average costs...")
 	RADIO (U"Cost function", 1)
 	RADIOBUTTON (U"Minimum-squared-error")
 	RADIOBUTTON (U"Minimum-cross-entropy")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	Activation him = FIRST (Activation);
-	Melder_information (FFNet_Pattern_Activation_getCosts_average (me, thee, him, GET_INTEGER (U"Cost function")));
+	PatternList thee = FIRST (PatternList);
+	ActivationList him = FIRST (ActivationList);
+	Melder_information (FFNet_PatternList_ActivationList_getCosts_average (me, thee, him, GET_INTEGER (U"Cost function")));
 END
 
-FORM (FFNet_Pattern_Activation_learnSD, U"FFNet & Pattern & Activation: Learn slow", 0)
+FORM (FFNet_PatternList_ActivationList_learnSD, U"FFNet & PatternList & ActivationList: Learn slow", 0)
 	NATURAL (U"Maximum number of epochs", U"100")
 	POSITIVE (U"Tolerance of minimizer", U"1e-7")
 	LABEL (U"Specifics", U"Specific for this minimization")
@@ -572,13 +573,13 @@ FORM (FFNet_Pattern_Activation_learnSD, U"FFNet & Pattern & Activation: Learn sl
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	Activation him = FIRST (Activation);
-	return FFNet_Pattern_Activation_learnSD (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
+	PatternList thee = FIRST (PatternList);
+	ActivationList him = FIRST (ActivationList);
+	return FFNet_PatternList_ActivationList_learnSD (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
 			GET_REAL (U"Tolerance of minimizer"), GET_REAL (U"Learning rate"), GET_REAL (U"Momentum"), GET_INTEGER (U"Cost function"));
 END
 
-FORM (FFNet_Pattern_Activation_learnSM, U"FFNet & Pattern & Activation: Learn", 0)
+FORM (FFNet_PatternList_ActivationList_learnSM, U"FFNet & PatternList & ActivationList: Learn", 0)
 	NATURAL (U"Maximum number of epochs", U"100")
 	POSITIVE (U"Tolerance of minimizer", U"1e-7")
 	RADIO (U"Cost function", 1)
@@ -587,46 +588,46 @@ FORM (FFNet_Pattern_Activation_learnSM, U"FFNet & Pattern & Activation: Learn",
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
-	Activation him = FIRST (Activation);
-	return FFNet_Pattern_Activation_learnSM (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
+	PatternList thee = FIRST (PatternList);
+	ActivationList him = FIRST (ActivationList);
+	return FFNet_PatternList_ActivationList_learnSM (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
 		GET_REAL (U"Tolerance of minimizer"), GET_INTEGER (U"Cost function"));
 END
 
-/*********** FFNet Pattern Categories **********************************/
+/*********** FFNet PatternList Categories **********************************/
 
-FORM (FFNet_Pattern_Categories_getCosts_total, U"FFNet & Pattern & Categories: Get total costs", U"FFNet & Pattern & Categories: Get total costs...")
+FORM (FFNet_PatternList_Categories_getCosts_total, U"FFNet & PatternList & Categories: Get total costs", U"FFNet & PatternList & Categories: Get total costs...")
 	RADIO (U"Cost function", 1)
 		RADIOBUTTON (U"Minimum-squared-error")
 		RADIOBUTTON (U"Minimum-cross-entropy")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
+	PatternList thee = FIRST (PatternList);
 	Categories him = FIRST (Categories);
-	Melder_information (FFNet_Pattern_Categories_getCosts_total (me, thee, him,
+	Melder_information (FFNet_PatternList_Categories_getCosts_total (me, thee, him,
 		GET_INTEGER (U"Cost function")));
 END
 
-FORM (FFNet_Pattern_Categories_getCosts_average, U"FFNet & Pattern & Categories: Get average costs", U"FFNet & Pattern & Categories: Get average costs...")
+FORM (FFNet_PatternList_Categories_getCosts_average, U"FFNet & PatternList & Categories: Get average costs", U"FFNet & PatternList & Categories: Get average costs...")
 	RADIO (U"Cost function", 1)
 		RADIOBUTTON (U"Minimum-squared-error")
 		RADIOBUTTON (U"Minimum-cross-entropy")
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
+	PatternList thee = FIRST (PatternList);
 	Categories him = FIRST (Categories);
-	Melder_information (FFNet_Pattern_Categories_getCosts_average (me, thee, him,
+	Melder_information (FFNet_PatternList_Categories_getCosts_average (me, thee, him,
 		GET_INTEGER (U"Cost function")));
 END
 
-FORM (Pattern_Categories_to_FFNet, U"Pattern & Categories: To FFNet", U"Pattern & Categories: To FFNet...")
+FORM (PatternList_Categories_to_FFNet, U"PatternList & Categories: To FFNet", U"PatternList & Categories: To FFNet...")
 	INTEGER (U"Number of units in hidden layer 1", U"0")
 	INTEGER (U"Number of units in hidden layer 2", U"0")
 	OK
 DO
-	Pattern me = FIRST (Pattern);
+	PatternList me = FIRST (PatternList);
 	Categories thee = FIRST (Categories);
 	long nHidden1 = GET_INTEGER (U"Number of units in hidden layer 1");
 	long nHidden2 = GET_INTEGER (U"Number of units in hidden layer 2");
@@ -648,7 +649,7 @@ DO
 	praat_new (ffnet.move(), ffnetName.peek());
 END
 
-FORM (FFNet_Pattern_Categories_learnSM, U"FFNet & Pattern & Categories: Learn", U"FFNet & Pattern & Categories: Learn...")
+FORM (FFNet_PatternList_Categories_learnSM, U"FFNet & PatternList & Categories: Learn", U"FFNet & PatternList & Categories: Learn...")
 	NATURAL (U"Maximum number of epochs", U"100")
 	POSITIVE (U"Tolerance of minimizer", U"1e-7")
 	RADIO (U"Cost function", 1)
@@ -657,13 +658,13 @@ FORM (FFNet_Pattern_Categories_learnSM, U"FFNet & Pattern & Categories: Learn",
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
+	PatternList thee = FIRST (PatternList);
 	Categories him = FIRST (Categories);
-	FFNet_Pattern_Categories_learnSM (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
+	FFNet_PatternList_Categories_learnSM (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
 		GET_REAL (U"Tolerance of minimizer"), GET_INTEGER (U"Cost function"));
 END
 
-FORM (FFNet_Pattern_Categories_learnSD, U"FFNet & Pattern & Categories: Learn slow", U"FFNet & Pattern & Categories: Learn slow...")
+FORM (FFNet_PatternList_Categories_learnSD, U"FFNet & PatternList & Categories: Learn slow", U"FFNet & PatternList & Categories: Learn slow...")
 	NATURAL (U"Maximum number of epochs", U"100")
 	POSITIVE (U"Tolerance of minimizer", U"1e-7")
 	LABEL (U"Specifics", U"Specific for this minimization")
@@ -675,12 +676,19 @@ FORM (FFNet_Pattern_Categories_learnSD, U"FFNet & Pattern & Categories: Learn sl
 	OK
 DO
 	FFNet me = FIRST (FFNet);
-	Pattern thee = FIRST (Pattern);
+	PatternList thee = FIRST (PatternList);
 	Categories him = FIRST (Categories);
-	FFNet_Pattern_Categories_learnSD (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
+	FFNet_PatternList_Categories_learnSD (me, thee, him, GET_INTEGER (U"Maximum number of epochs"),
 		GET_REAL (U"Tolerance of minimizer"), GET_REAL (U"Learning rate"), GET_REAL (U"Momentum"), GET_INTEGER (U"Cost function"));
 END
 
+DIRECT (RBM_PatternList_to_ActivationList)
+	iam_ONLY (RBM);
+	thouart_ONLY (PatternList);
+	autoActivationList him = RBM_PatternList_to_ActivationList (me, thee);
+	praat_new (him.move(), my name, U"_", thy name);
+END
+
 void praat_uvafon_FFNet_init ();
 void praat_uvafon_FFNet_init () {
 	Thing_recognizeClassesByName (classFFNet, NULL);
@@ -691,7 +699,7 @@ void praat_uvafon_FFNet_init () {
 	praat_addMenuCommand (U"Objects", U"New", U"Create iris example...", 0, 1, DO_FFNet_createIrisExample);
 	praat_addMenuCommand (U"Objects", U"New", U"Create FFNet...", 0, 1, DO_FFNet_create);
 	praat_addMenuCommand (U"Objects", U"New", U"Advanced", 0, 1, 0);
-	praat_addMenuCommand (U"Objects", U"New", U"Create Pattern...", 0, 2, DO_Pattern_create);
+	praat_addMenuCommand (U"Objects", U"New", U"Create PatternList...", 0, 2, DO_PatternList_create);
 	praat_addMenuCommand (U"Objects", U"New", U"Create Categories...", 0, 2, DO_Categories_create);
 	praat_addMenuCommand (U"Objects", U"New", U"Create FFNet (linear outputs)...", 0, 2, DO_FFNet_create_linearOutputs);
 
@@ -723,42 +731,44 @@ void praat_uvafon_FFNet_init () {
 	praat_addAction1 (classFFNet, 0, EXTRACT_BUTTON, 0, 0, 0);
 	praat_addAction1 (classFFNet, 0, U"Extract weights...", 0, 1, DO_FFNet_extractWeights);
 	praat_addAction1 (classFFNet, 0, U"Weights to Matrix...", 0, praat_DEPTH_1 | praat_HIDDEN, DO_FFNet_weightsToMatrix);
-	praat_addAction1 (classFFNet, 0, U"& Pattern: Classify?", 0, 0, DO_hint_FFNet_and_Pattern_classify);
-	praat_addAction1 (classFFNet, 0, U"& Pattern & Categories: Learn?", 0, 0, DO_hint_FFNet_and_Pattern_and_Categories_learn);
+	praat_addAction1 (classFFNet, 0, U"& PatternList: Classify?", 0, 0, DO_hint_FFNet_and_PatternList_classify);
+	praat_addAction1 (classFFNet, 0, U"& PatternList & Categories: Learn?", 0, 0, DO_hint_FFNet_and_PatternList_and_Categories_learn);
 
-	praat_addAction2 (classFFNet, 1, classActivation, 1, U"Analyse", 0, 0, 0);
-	praat_addAction2 (classFFNet, 1, classActivation, 1, U"To Categories...", 0, 0, DO_FFNet_Activation_to_Categories);
+	praat_addAction2 (classFFNet, 1, classActivationList, 1, U"Analyse", 0, 0, 0);
+	praat_addAction2 (classFFNet, 1, classActivationList, 1, U"To Categories...", 0, 0, DO_FFNet_ActivationList_to_Categories);
 
 	praat_addAction2 (classFFNet, 1, classEigen, 1, U"Draw", 0, 0, 0);
 	praat_addAction2 (classFFNet, 1, classEigen, 1, U"Draw hyperplane intersections", 0, 0, DO_FFNet_Eigen_drawIntersection);
 
 	praat_addAction2 (classFFNet, 1, classCategories, 1, U"Analyse", 0, 0, 0);
-	praat_addAction2 (classFFNet, 1, classCategories, 1, U"To Activation", 0, 0, DO_FFNet_Categories_to_Activation);
+	praat_addAction2 (classFFNet, 1, classCategories, 1, U"To ActivationList", 0, 0, DO_FFNet_Categories_to_ActivationList);
 
 	praat_addAction2 (classFFNet, 1, classMatrix, 1, U"Modify", 0, 0, 0);
 	praat_addAction2 (classFFNet, 1, classMatrix, 1, U"Weights from Matrix...", 0, 0, DO_FFNet_weightsFromMatrix);
 
-	praat_addAction2 (classFFNet, 1, classPattern, 1, U"Draw", 0, 0, 0);
-	praat_addAction2 (classFFNet, 1, classPattern, 1, U"Draw activation...", 0, 0, DO_FFNet_Pattern_drawActivation);
-	praat_addAction2 (classFFNet, 1, classPattern, 1, U"Analyse", 0, 0, 0);
-	praat_addAction2 (classFFNet, 1, classPattern, 1, U"To Categories...", 0, 0, DO_FFNet_Pattern_to_Categories);
-	praat_addAction2 (classFFNet, 1, classPattern, 1, U"To Activation...", 0, 0, DO_FFNet_Pattern_to_Activation);
+	praat_addAction2 (classFFNet, 1, classPatternList, 1, U"Draw", 0, 0, 0);
+	praat_addAction2 (classFFNet, 1, classPatternList, 1, U"Draw activation...", 0, 0, DO_FFNet_PatternList_drawActivation);
+	praat_addAction2 (classFFNet, 1, classPatternList, 1, U"Analyse", 0, 0, 0);
+	praat_addAction2 (classFFNet, 1, classPatternList, 1, U"To Categories...", 0, 0, DO_FFNet_PatternList_to_Categories);
+	praat_addAction2 (classFFNet, 1, classPatternList, 1, U"To ActivationList...", 0, 0, DO_FFNet_PatternList_to_ActivationList);
 
 	praat_addAction2 (classFFNet, 1, classPCA, 1, U"Draw decision plane...", 0, 0, DO_FFNet_PCA_drawDecisionPlaneInEigenspace);
-
-	praat_addAction2 (classPattern, 1, classCategories, 1, U"To FFNet...", 0, 0, DO_Pattern_Categories_to_FFNet);
-
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classActivation, 1, U"Get total costs...", 0, 0, DO_FFNet_Pattern_Activation_getCosts_total);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classActivation, 1, U"Get average costs...", 0, 0, DO_FFNet_Pattern_Activation_getCosts_average);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classActivation, 1, U"Learn", 0, 0, 0);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classActivation, 1, U"Learn...", 0, 0, DO_FFNet_Pattern_Activation_learnSM);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classActivation, 1, U"Learn slow...", 0, 0, DO_FFNet_Pattern_Activation_learnSD);
-
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classCategories, 1, U"Get total costs...", 0, 0, DO_FFNet_Pattern_Categories_getCosts_total);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classCategories, 1, U"Get average costs...", 0, 0, DO_FFNet_Pattern_Categories_getCosts_average);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classCategories, 1, U"Learn", 0, 0, 0);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classCategories, 1, U"Learn...", 0, 0, DO_FFNet_Pattern_Categories_learnSM);
-	praat_addAction3 (classFFNet, 1, classPattern, 1, classCategories, 1, U"Learn slow...", 0, 0, DO_FFNet_Pattern_Categories_learnSD);
+	
+	praat_addAction2 (classRBM, 1, classPatternList, 1, U"To ActivationList", 0, 0, DO_RBM_PatternList_to_ActivationList);
+
+	praat_addAction2 (classPatternList, 1, classCategories, 1, U"To FFNet...", 0, 0, DO_PatternList_Categories_to_FFNet);
+
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classActivationList, 1, U"Get total costs...", 0, 0, DO_FFNet_PatternList_ActivationList_getCosts_total);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classActivationList, 1, U"Get average costs...", 0, 0, DO_FFNet_PatternList_ActivationList_getCosts_average);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classActivationList, 1, U"Learn", 0, 0, 0);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classActivationList, 1, U"Learn...", 0, 0, DO_FFNet_PatternList_ActivationList_learnSM);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classActivationList, 1, U"Learn slow...", 0, 0, DO_FFNet_PatternList_ActivationList_learnSD);
+
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classCategories, 1, U"Get total costs...", 0, 0, DO_FFNet_PatternList_Categories_getCosts_total);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classCategories, 1, U"Get average costs...", 0, 0, DO_FFNet_PatternList_Categories_getCosts_average);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classCategories, 1, U"Learn", 0, 0, 0);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classCategories, 1, U"Learn...", 0, 0, DO_FFNet_PatternList_Categories_learnSM);
+	praat_addAction3 (classFFNet, 1, classPatternList, 1, classCategories, 1, U"Learn slow...", 0, 0, DO_FFNet_PatternList_Categories_learnSD);
 
 	INCLUDE_MANPAGES (manual_FFNet_init)
 }
diff --git a/LPC/LPC_and_LineSpectralFrequencies.cpp b/LPC/LPC_and_LineSpectralFrequencies.cpp
new file mode 100644
index 0000000..0858259
--- /dev/null
+++ b/LPC/LPC_and_LineSpectralFrequencies.cpp
@@ -0,0 +1,305 @@
+/* LPC_and_LineSpectralFrequencies.cpp
+ *
+ * Copyright (C) 2016 David Weenink
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ djmw 20160421  Initial version
+*/
+
+#include "LPC_and_LineSpectralFrequencies.h"
+#include "NUM2.h"
+#include "Polynomial.h"
+
+
+/* Conversion from Y(w) to a polynomial in x (= 2 cos (w))
+ * From: Joseph Rothweiler (1999), "On Polynomial Reduction in the Computation of LSP Frequencies." 
+ * 	IEEE Trans. on ASSP 7, 592--594.
+ */
+static void cos2x (double *g, long order) {
+	for (long i = 2; i <= order; i ++) {
+		for (long j = order; j > i; j--) {
+			g [j - 2] -= g[j];
+		}
+		g [i - 2] -= 2.0 * g [i];
+	}
+}
+
+static void Polynomial_fromLPC_Frame_lspsum (Polynomial me, LPC_Frame lpc) {
+	/* Fs (z) = A(z) + z^-(p+1) A(1/z) */
+	
+	long order = lpc -> nCoefficients, g_order = (order + 1) / 2; // orders
+	my coefficients [order + 2] = 1.0;
+	for (long i = 1; i <= order; i++) {
+		my coefficients [order + 2 - i] = lpc -> a [i] + lpc -> a [order + 1 - i];
+	}
+	my coefficients [1] = 1.0;
+	my numberOfCoefficients = order + 2;
+
+	if (order % 2 == 0) { // order even
+		Polynomial_divide_firstOrderFactor (me, -1.0, nullptr);
+	}
+	/* transform to cos(w) terms */
+	for (long i = 1; i <= g_order + 1; i++) {
+		my coefficients [i] = my coefficients [g_order + i];
+	}
+	my numberOfCoefficients = g_order + 1;
+	/* to Chebychev */
+	cos2x (& my coefficients [1], g_order);
+}
+
+static void Polynomial_fromLPC_Frame_lspdif (Polynomial me, LPC_Frame lpc) {
+	/* Fa (z) = A(z) - z^-(p+1)A(1/z) */
+	long order = lpc -> nCoefficients;
+	my coefficients [order + 2] = -1.0;
+	for (long i = 1; i <= order; i++) {
+		my coefficients [order + 2 - i] = -lpc -> a [i] + lpc -> a [order + 1 - i];
+	}
+	my coefficients [1] = 1.0;
+	my numberOfCoefficients = order + 2;
+
+	if (order % 2 == 0) { // Fa(z)/(z-1)
+		Polynomial_divide_firstOrderFactor (me, 1.0, nullptr);
+	} else { // Fa(z) / (z^2 - 1)
+		Polynomial_divide_secondOrderFactor (me, 1.0);
+	}
+	/* transform to cos(w) terms */
+	long g_order = my numberOfCoefficients / 2;
+	for (long i = 1; i <= g_order + 1; i++) {
+		my coefficients [i] = my coefficients [g_order + i];
+	}
+	my numberOfCoefficients = g_order + 1;
+	/* to Chebychev */
+	cos2x (& my coefficients [1], g_order);
+}
+
+#if 0
+/* g[0]+g[1]x + ... g[m]*x^ m = 0 ; m must be even
+ * Semenov, Kalyuzhny, Kovtonyuk (2003), Efficient calculation of line spectral frequencies based on new method for solution of transcendental equations,
+ * ICASSP 2003, 457--460
+ * 		g[0 .. g_order]
+ * 		work [0.. g_order + 1 + (numberOfDerivatives + 1) * 5]
+ * 		root [1 .. (g_order+1)/2]
+ */
+static void Roots_fromPolynomial (Roots me, Polynomial g, long numberOfDerivatives, double *work) {
+	if (numberOfDerivatives < 3) {
+		Melder_throw (U"Number of derivatives must be at least 3.");
+	}
+	double xmin = -1.0, xmax = 1.0;
+	long numberOfRootsFound = 0;
+	long g_order = g -> numberOfCoefficients - 1;
+	double *gabs = work, *fact = gabs + g_order + 1, *p2 = fact + numberOfDerivatives + 1;
+	double *derivatives = p2 + numberOfDerivatives + 1, *constraints = derivatives + numberOfDerivatives + 1;
+	double *intervals = constraints + numberOfDerivatives + 1;
+	
+	/* Fill vectors with j! and 2^j only once */
+	fact [0] = p2 [0] = 1.0;
+	for (long j = 1; j <= numberOfDerivatives; j ++) {
+		fact [j] = fact [j - 1] * j; // j!
+		p2 [j] = p2 [j - 1] * 2.0; // 2^j
+	}
+	
+	/* The constraints M[j] (Semenov et al. eq. (8)) can be calculated by taking absolute values of 
+	 * the polynomial coefficients and evaluating the polynomial and the derivatives at x = 1.0
+	 */
+	for (long k = 0; k <= g_order; k++) {
+		gabs [k] = fabs (g -> coefficients [k + 1]);
+	}
+	evaluatePolynomialAndDerivatives (gabs, g_order, 1.0, constraints, numberOfDerivatives);
+	intervals [0] = 1.0;
+	while (numberOfRootsFound < g_order || xmin == xmax) {
+		double dsum1 = 0.0, dsum2 = 0.0;
+		double xmid = (xmin + xmax) / 2.0;
+		evaluatePolynomialAndDerivatives (g, g_order, xmid, derivatives, numberOfDerivatives);
+		double fxmid = derivatives[0], fdxmin = derivatives[1];
+		long j = 1;
+		bool rootsOnIntervalPossible_f = true, rootsOnIntervalPossible_df = true;
+		while (j <= numberOfDerivatives && (rootsOnIntervalPossible_f || rootsOnIntervalPossible_df)) {
+			intervals [j] = intervals [j - 1] * (xmax - xmin);
+			long k = j - 1;
+			if (j > 1) { // start at first derivative
+				dsum1 += fabs (derivatives [k]) * intervals [k] / (p2 [k] * fact [k]);
+			}
+			if (j > 2) { // start at second derivative
+				dsum2 += fabs (derivatives [k]) * intervals [k - 1] / (p2 [k - 1] * fact [k - 1]);
+				if (rootsOnIntervalPossible_f) {
+					double testValue1 = dsum1 + constraints [j] * intervals [j] / (p2 [j] * fact [j]);
+					rootsOnIntervalPossible_f = ! (fxmid + testValue1 < 0 || fxmid - testValue1 > 0);
+				}
+				if (rootsOnIntervalPossible_df) {
+					double testValue2 = dsum2 + constraints [j] * intervals [j - 1] / (p2 [j - 1] * fact [j - 1]);
+					rootsOnIntervalPossible_df = ! (fdxmin + testValue2 < 0 || fdxmin - testValue2 > 0);
+				}
+			}
+			j++;
+		}
+		if (rootsOnIntervalPossible_f) {
+			if (rootsOnIntervalPossible_df) { // f(x) uncertain && f'(x) uncertain : bisect
+				xmax = xmid;
+			} else {// f(x) uncertain; f'(x) certain
+				double fxmin = evaluatePolynomial (g, g_order, xmin);
+				double fxmax = evaluatePolynomial (g, g_order, xmax);
+				if (fxmin * fxmax <= 0.0) {
+					double root;
+					NUMnrbis (dpoly, xmin, xmax, &poly, &root);
+					roots [++numberOfRootsFound] = root;
+				} else {
+					xmin = xmax; xmax = 1.0;
+				}
+			}
+		} else {
+			xmin = xmax; xmax = 1.0;
+		}
+	}	
+}
+#endif
+
+static long Roots_fromPolynomial_grid (Roots me, Polynomial thee, double gridSize) {
+	Melder_assert (my max >= thy numberOfCoefficients - 1);
+	double xmin = thy xmin;
+	long numberOfRootsFound = 0;
+	while (xmin < thy xmax && numberOfRootsFound < thy numberOfCoefficients - 1) {
+		double xmax = xmin + gridSize;
+		xmax = xmax > thy xmax ? thy xmax : xmax;
+		//double root = Polynomial_findOneRealRoot_nr (thee, xmin, xmax);
+		double root = Polynomial_findOneSimpleRealRoot_ridders (thee, xmin, xmax);
+		if (root != NUMundefined && (numberOfRootsFound == 0 || my v [numberOfRootsFound].re != root)) {
+			my v [++numberOfRootsFound].re = root; // root not at border of interval
+			my v [numberOfRootsFound].im = 0.0;
+		}
+		xmin = xmax;
+	}
+	// make rest of storage undefined (not necessary)
+	return numberOfRootsFound;
+}
+
+void LineSpectralFrequencies_Frame_initFromLPC_Frame_grid (LineSpectralFrequencies_Frame me, LPC_Frame thee, Polynomial g1, Polynomial g2, Roots roots, double gridSize, double maximumFrequency) {
+	/* Construct Fs and Fa
+		* divide out the zeros
+		* transform to polynomial equations g1 and g2 of half the order
+		*/
+	LineSpectralFrequencies_Frame_init (me, thy nCoefficients);
+	Polynomial_fromLPC_Frame_lspsum (g1, thee);
+	long half_order_g1 = g1 -> numberOfCoefficients - 1;
+	Polynomial_fromLPC_Frame_lspdif (g2, thee);
+	long half_order_g2 = g2 -> numberOfCoefficients - 1;
+	
+	long numberOfBisections = 0, numberOfRootsFound = 0;
+	while (numberOfRootsFound  < half_order_g1 && numberOfBisections < 10) {
+		numberOfRootsFound = Roots_fromPolynomial_grid (roots, g1, gridSize);
+		gridSize *= 0.5; numberOfBisections++;
+	}
+	if (numberOfBisections == 10) {
+		Melder_throw (U"Too many bisections.");
+	}
+	// [g1-> xmin, g1 -> xmax] <==> [nyquistFrequency, 0] i.e. highest root corresponds to lowest frequency
+	for (long i = 1; i <= half_order_g1; i++) {
+		my frequencies [2 * i - 1] = acos (roots -> v [half_order_g1 + 1 - i].re / 2.0) / NUMpi * maximumFrequency; 
+	}
+	// the roots of g2 lie inbetween the roots of g1
+	for (long i = 1; i <= half_order_g2; i++) {
+		double xmax = roots -> v [half_order_g1 + 1 - i].re;
+		double xmin = i == half_order_g1 ? g1 -> xmin : roots -> v [half_order_g1 - i].re;
+		double root = Polynomial_findOneSimpleRealRoot_ridders (g2, xmin, xmax);
+		if (root != NUMundefined) {
+			my frequencies [2 * i] = acos (root / 2.0) / NUMpi * maximumFrequency;
+		} else { 
+			my numberOfFrequencies --;
+		}	
+	}
+}
+
+autoLineSpectralFrequencies LPC_to_LineSpectralFrequencies (LPC me, double gridSize) {
+	try {
+		if (gridSize == 0.0) {
+			gridSize = 0.02;
+		}
+		double nyquistFrequency = 0.5 / my samplingPeriod;
+		autoLineSpectralFrequencies thee = LineSpectralFrequencies_create (my xmin, my xmax, my nx, my dx, my x1, my maxnCoefficients, nyquistFrequency);
+		autoPolynomial g1 = Polynomial_create (-2.0, 2.0, my maxnCoefficients + 1); // large enough
+		autoPolynomial g2 = Polynomial_create (-2.0, 2.0, my maxnCoefficients + 1);
+		autoRoots roots = Roots_create ((my maxnCoefficients + 1) / 2);
+		
+		for (long iframe = 1; iframe <= my nx; iframe ++) {
+			LPC_Frame lpf = & my d_frames [iframe];
+			LineSpectralFrequencies_Frame lsf = & thy d_frames [iframe];
+			/* Construct Fs and Fa
+			 * divide out the zeros
+			 * transform to polynomial equations g1 and g2 of half the order
+			 * find zeros
+			 */
+			LineSpectralFrequencies_Frame_initFromLPC_Frame_grid (lsf, lpf, g1.get(), g2.get(), roots.get(), gridSize, nyquistFrequency);
+		}
+		return thee;
+	} catch (MelderError) {
+		Melder_throw (me, U": no LineSpectralFrequencies created.");
+	}
+}
+
+void LPC_Frame_initFromLineSpectralFrequencies_Frame (LPC_Frame me, LineSpectralFrequencies_Frame thee, Polynomial fs, Polynomial fa, double maximumFrequency) {
+	LPC_Frame_init (me, thy numberOfFrequencies);
+
+	/* Reconstruct Fs (z) */
+	long numberOfOmegas = (thy numberOfFrequencies + 1) / 2;
+	for (long i = 1; i <= numberOfOmegas; i++) {
+		double omega = thy frequencies [2 * i -1] / maximumFrequency * NUMpi;
+		my a[i] = -2.0 * cos (omega);
+	}
+	Polynomial_initFromProductOfSecondOrderTerms (fs, my a, numberOfOmegas);
+
+	/* Reconstruct Fa (z) */
+	numberOfOmegas = thy numberOfFrequencies / 2;
+	for (long i = 1; i <= numberOfOmegas; i++) {
+		double omega = thy frequencies [2 * i] / maximumFrequency * NUMpi;
+		my a [i] = -2.0 * cos (omega);
+	}
+	Polynomial_initFromProductOfSecondOrderTerms (fa, my a, numberOfOmegas);
+	
+	if (thy numberOfFrequencies % 2 == 0) {
+		Polynomial_multiply_firstOrderFactor (fs, -1.0); // * (z + 1)
+		Polynomial_multiply_firstOrderFactor (fa, 1.0); // * (z - 1)
+	} else {
+		Polynomial_multiply_secondOrderFactor (fa, 1.0); // * (z^2 - 1)
+	}
+	Melder_assert (fs -> numberOfCoefficients == fa -> numberOfCoefficients);
+	/* A(z) = (Fs(z) + Fa(z) / 2 */
+	for (long i = 1; i <= fs -> numberOfCoefficients - 2; i++) {
+		my a [thy numberOfFrequencies - i + 1] = 0.5 * (fs -> coefficients [i+1] + fa -> coefficients [i+1]);
+	}
+}
+
+autoLPC LineSpectralFrequencies_to_LPC (LineSpectralFrequencies me) {
+	try {
+		autoLPC thee = LPC_create (my xmin, my xmax, my nx, my dx, my x1, my maximumNumberOfFrequencies,0.5 / my maximumFrequency);
+		autoPolynomial fs = Polynomial_create (-1.0, 1.0, my maximumNumberOfFrequencies + 2);
+		autoPolynomial fa = Polynomial_create (-1.0, 1.0, my maximumNumberOfFrequencies + 2);
+		
+		for (long iframe = 1; iframe <= my nx; iframe ++) {
+			LineSpectralFrequencies_Frame lsf = & my d_frames [iframe];
+			LPC_Frame lpf = & thy d_frames [iframe];
+			/* Construct Fs and Fa
+			 * A(z) = (Fs(z) + Fa(z))/2
+			 */
+			LPC_Frame_initFromLineSpectralFrequencies_Frame (lpf, lsf, fs.get(), fa.get(), my maximumFrequency);
+		}
+		return thee;
+	} catch (MelderError) {
+		Melder_throw (me, U": no LPC created from LineSpectralFrequencies.");
+	}
+}
+
+/* End of file LPC_and_LineSpectralFrequencies.cpp */
diff --git a/LPC/LPC_and_LineSpectralFrequencies.h b/LPC/LPC_and_LineSpectralFrequencies.h
new file mode 100644
index 0000000..820a4f2
--- /dev/null
+++ b/LPC/LPC_and_LineSpectralFrequencies.h
@@ -0,0 +1,30 @@
+#ifndef _LPC_and_LineSpectralFrequencies_h_
+#define _LPC_and_LineLineSpectralFrequencies_h_
+/* LPC_and_LineLineSpectralFrequencies.h
+ *
+ * Copyright (C) 2016 David Weenink
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "LPC.h"
+#include "LineSpectralFrequencies.h"
+
+autoLineSpectralFrequencies LPC_to_LineSpectralFrequencies (LPC me, double gridSize);
+
+autoLPC LineSpectralFrequencies_to_LPC (LineSpectralFrequencies me);
+
+
+#endif /* _LPC_and_LineSpectralFrequencies_h_ */
diff --git a/LPC/LPC_and_Polynomial.cpp b/LPC/LPC_and_Polynomial.cpp
index 21742aa..4b69ea0 100644
--- a/LPC/LPC_and_Polynomial.cpp
+++ b/LPC/LPC_and_Polynomial.cpp
@@ -1,6 +1,6 @@
 /* LPC_and_Polynomial.cpp
  *
- * Copyright (C) 1994-2011, 2015 David Weenink
+ * Copyright (C) 1994-2011, 2015-2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,7 +28,7 @@ autoPolynomial LPC_Frame_to_Polynomial (LPC_Frame me) {
 	for (long i = 1; i <= degree; i++) {
 		thy coefficients[i] = my a[degree - i + 1];
 	}
-	thy coefficients[degree + 1] = 1;
+	thy coefficients[degree + 1] = 1.0;
 	return thee;
 }
 
diff --git a/LPC/LPC_to_Spectrum.cpp b/LPC/LPC_to_Spectrum.cpp
index 1821c61..c3883b7 100644
--- a/LPC/LPC_to_Spectrum.cpp
+++ b/LPC/LPC_to_Spectrum.cpp
@@ -33,8 +33,7 @@
 	LPC-spectrum is approximately 20 dB too high (w.r.t. 25 ms spectrum from Sound)
 */
 
-void LPC_Frame_into_Spectrum (LPC_Frame me, Spectrum thee, double bandwidthReduction,
-                              double deEmphasisFrequency) {
+void LPC_Frame_into_Spectrum (LPC_Frame me, Spectrum thee, double bandwidthReduction, double deEmphasisFrequency) {
 	if (my nCoefficients == 0) {
 		for (long i = 1; i <= thy nx; i++) {
 			thy z[1][i] = thy z[2][i] = 0.0;
diff --git a/LPC/LineSpectralFrequencies.cpp b/LPC/LineSpectralFrequencies.cpp
new file mode 100644
index 0000000..6dd0302
--- /dev/null
+++ b/LPC/LineSpectralFrequencies.cpp
@@ -0,0 +1,140 @@
+/* LineSpectralFrequencies.cpp
+ *
+ * Copyright (C) 2016 David Weenink
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ djmw 20160421  Initial version
+*/
+
+#include "LineSpectralFrequencies.h"
+#include "NUM2.h"
+
+#include "oo_DESTROY.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_COPY.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_EQUAL.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_CAN_WRITE_AS_ENCODING.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_WRITE_TEXT.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_WRITE_BINARY.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_READ_TEXT.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_READ_BINARY.h"
+#include "LineSpectralFrequencies_def.h"
+#include "oo_DESCRIPTION.h"
+#include "LineSpectralFrequencies_def.h"
+
+Thing_implement (LineSpectralFrequencies, Sampled, 1);
+
+void structLineSpectralFrequencies :: v_info () {
+	structDaata :: v_info ();
+	MelderInfo_writeLine (U"Time domain: ", xmin, U" to ", xmax, U" (s).");
+	MelderInfo_writeLine (U"Number of frequencies: ", maximumNumberOfFrequencies);
+	MelderInfo_writeLine (U"Number of frames: ", nx);
+	MelderInfo_writeLine (U"Time step: ", dx, U" (s).");
+	MelderInfo_writeLine (U"First frame at: ", x1, U" (s).");
+}
+
+void LineSpectralFrequencies_Frame_init (LineSpectralFrequencies_Frame me, int numberOfFrequencies) {
+	my frequencies = NUMvector<double> (1, numberOfFrequencies);
+	my numberOfFrequencies = numberOfFrequencies;
+}
+
+void LineSpectralFrequencies_init (LineSpectralFrequencies me, double tmin, double tmax, long nt, double dt, double t1, int numberOfFrequencies, double maximumFrequency) {
+	my maximumFrequency = maximumFrequency;
+	my maximumNumberOfFrequencies = numberOfFrequencies;
+	Sampled_init (me, tmin, tmax, nt, dt, t1);
+	my d_frames = NUMvector<structLineSpectralFrequencies_Frame> (1, nt);
+}
+
+autoLineSpectralFrequencies LineSpectralFrequencies_create (double tmin, double tmax, long nt, double dt, double t1, int numberOfFrequencies, double maximumFrequency) {
+	try {
+		autoLineSpectralFrequencies me = Thing_new (LineSpectralFrequencies);
+		LineSpectralFrequencies_init (me.get(), tmin, tmax, nt, dt, t1, numberOfFrequencies, maximumFrequency);
+		return me;
+	} catch (MelderError) {
+		Melder_throw (U"LineSpectralFrequencies not created.");
+	}
+}
+
+void LineSpectralFrequencies_drawFrequencies (LineSpectralFrequencies me, Graphics g, double tmin, double tmax, double fmin, double fmax, bool garnish) {
+	if (tmax <= tmin) {
+		tmin = my xmin;
+		tmax = my xmax;
+	}
+	long itmin, itmax;
+	if (! Sampled_getWindowSamples (me, tmin, tmax, & itmin, & itmax)) {
+		return;
+	}
+	if (fmax <= fmin) {
+		double f1max, f2min; 
+		autoNUMvector<double> f1 (itmin, itmax), f2 (itmin, itmax);
+		for (long iframe = itmin; iframe <= itmax; iframe++) {
+			f1[iframe] = my d_frames[iframe].frequencies[1];
+			f2[iframe] = my d_frames[iframe].frequencies[my d_frames[iframe].numberOfFrequencies];
+		}
+		NUMvector_extrema (f1.peek(), itmin, itmax, & fmin, & f1max);
+		NUMvector_extrema (f2.peek(), itmin, itmax, & f2min, & fmax);
+	}
+	if (fmax == fmin) {
+		fmin = 0;
+		fmax += 0.5;
+	}
+
+	Graphics_setInner (g);
+	Graphics_setWindow (g, tmin, tmax, fmin, fmax);
+	for (long iframe = itmin; iframe <= itmax; iframe++) {
+		LineSpectralFrequencies_Frame lsf = & my d_frames[iframe];
+		double x = Sampled_indexToX (me, iframe);
+		for (long ifreq = 1; ifreq <= lsf -> numberOfFrequencies; ifreq++) {
+			double y = lsf -> frequencies [ifreq];
+			if (y >= fmin && y <= fmax) { 
+				Graphics_speckle (g, x, y);
+			}
+		}
+	}
+	Graphics_unsetInner (g);
+	if (garnish) {
+		Graphics_drawInnerBox (g);
+		Graphics_textBottom (g, true, U"Time (seconds)");
+		Graphics_textLeft (g, true, U"Frequency (Hz)");
+		Graphics_marksBottom (g, 2, true, true, false);
+		Graphics_marksLeft (g, 2, true, true, false);
+	}
+}
+
+autoMatrix LineSpectralFrequencies_downto_Matrix (LineSpectralFrequencies me) {
+	try {
+		autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, 0.5, 0.5 + my maximumNumberOfFrequencies, my maximumNumberOfFrequencies, 1.0, 1.0);
+		for (long j = 1; j <= my nx; j++) {
+			LineSpectralFrequencies_Frame lsf = & my d_frames[j];
+			for (long i = 1; i <= lsf -> numberOfFrequencies; i++) {
+				thy z [i][j] = lsf -> frequencies [i];
+			}
+		}
+		return thee;
+	} catch (MelderError) {
+		Melder_throw (me, U": no Matrix with linear prediction coefficients created.");
+	}
+}
+
+/* End of file LineSpectralFrequencies.cpp */
diff --git a/LPC/LineSpectralFrequencies.h b/LPC/LineSpectralFrequencies.h
new file mode 100644
index 0000000..fbc3b0d
--- /dev/null
+++ b/LPC/LineSpectralFrequencies.h
@@ -0,0 +1,50 @@
+#ifndef _LineSpectralFrequencies_h_
+#define _LineSpectralFrequencies_h_
+/* LineSpectralFrequencies.h
+ *
+ * Copyright (C) 2016 David Weenink
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "LPC.h"
+#include "Matrix.h"
+#include "Graphics.h"
+
+#include "LineSpectralFrequencies_def.h"
+
+/*
+	From Sampled:
+	xmin, xmax : range of time (s)
+	x1 : position of first frame (s)
+	dx : step size (s)
+	nx : number of frames
+*/
+
+void LineSpectralFrequencies_init (LineSpectralFrequencies me, double tmin, double tmax, long nt, double dt, double t1, long numberOfFrequencies, double maximumFrequency);
+
+autoLineSpectralFrequencies LineSpectralFrequencies_create (double tmin, double tmax, long nt, double dt, double t1, int numberOfFrequencies, double maximumFrequency);
+
+void LineSpectralFrequencies_drawFrequencies (LineSpectralFrequencies me, Graphics g, double fromTime, double toTime, double fmin, double fmax, bool garnish);
+
+autoMatrix LineSpectralFrequencies_downto_Matrix (LineSpectralFrequencies me);
+
+/******************* Frames ************************************************/
+
+void LineSpectralFrequencies_Frame_init (LineSpectralFrequencies_Frame me, int numberOfFrequencies);
+
+
+
+#endif /* _LineSpectralFrequencies_h_ */
diff --git a/LPC/LineSpectralFrequencies_def.h b/LPC/LineSpectralFrequencies_def.h
new file mode 100644
index 0000000..fcfcaa3
--- /dev/null
+++ b/LPC/LineSpectralFrequencies_def.h
@@ -0,0 +1,47 @@
+/* LineSpectralFrequencies_def.h
+ *
+ * Copyright (C) 2016 David Weenink
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#define ooSTRUCT LineSpectralFrequencies_Frame
+oo_DEFINE_STRUCT (LineSpectralFrequencies_Frame)
+
+	oo_INT (numberOfFrequencies)
+	oo_DOUBLE_VECTOR (frequencies, numberOfFrequencies)
+	
+oo_END_STRUCT (LineSpectralFrequencies_Frame)
+#undef ooSTRUCT
+
+
+#define ooSTRUCT LineSpectralFrequencies
+oo_DEFINE_CLASS (LineSpectralFrequencies, Sampled)
+
+	oo_DOUBLE (maximumFrequency)
+	oo_INT (maximumNumberOfFrequencies)
+	oo_STRUCT_VECTOR (LineSpectralFrequencies_Frame, d_frames, nx)
+
+	#if oo_DECLARING
+		void v_info ()
+			override;
+	#endif
+
+oo_END_CLASS (LineSpectralFrequencies)
+#undef ooSTRUCT
+
+
+/* End of file LineSpectralFrequencies_def.h */
diff --git a/LPC/Makefile b/LPC/Makefile
index 928cc09..3fa4da5 100644
--- a/LPC/Makefile
+++ b/LPC/Makefile
@@ -7,9 +7,9 @@ CPPFLAGS = -I ../num -I ../dwtools -I ../fon -I ../sys -I ../dwsys -I ../stat
 
 OBJECTS = Cepstrum.o Cepstrumc.o Cepstrum_and_Spectrum.o \
 	Cepstrogram.o \
-	Formant_extensions.o \
+	Formant_extensions.o LineSpectralFrequencies.o \
 	LPC.o LPC_and_Cepstrumc.o LPC_and_Formant.o LPC_and_LFCC.o \
-	LPC_and_Polynomial.o \
+	LPC_and_LineSpectralFrequencies.o LPC_and_Polynomial.o \
 	LPC_to_Spectrum.o  LPC_to_Spectrogram.o \
 	LPC_and_Tube.o \
 	Sound_and_LPC.o  Sound_and_LPC_robust.o \
diff --git a/LPC/Sound_and_LPC.cpp b/LPC/Sound_and_LPC.cpp
index ea2d2ba..17e56af 100644
--- a/LPC/Sound_and_LPC.cpp
+++ b/LPC/Sound_and_LPC.cpp
@@ -88,7 +88,7 @@ static int Sound_into_LPC_Frame_auto (Sound me, LPC_Frame thee) {
 	if (r[1] == 0.0) {
 		i = 1; /* ! */ goto end;
 	}
-	a[1] = 1; a[2] = rc[1] = - r[2] / r[1];
+	a[1] = 1.0; a[2] = rc[1] = - r[2] / r[1];
 	thy gain = r[1] + r[2] * rc[1];
 	for (i = 2; i <= m; i++) {
 		double s = 0.0;
@@ -373,9 +373,10 @@ static autoLPC _Sound_to_LPC (Sound me, int predictionOrder, double analysisWidt
 	double windowDuration = 2 * analysisWidth; /* gaussian window */
 	long nFrames, frameErrorCount = 0;
 
-	if (floor (windowDuration / my dx) < predictionOrder + 1) Melder_throw (U"Analysis window duration too short.\n"
-		        U"For a prediction order of ", predictionOrder, U" the analysis window duration has to be greater than ", my dx * (predictionOrder + 1),
-		        U"Please increase the analysis window duration or lower the prediction order.");
+	if (floor (windowDuration / my dx) < predictionOrder + 1) {
+		Melder_throw (U"Analysis window duration too short.\n For a prediction order of ", predictionOrder,
+			U" the analysis window duration has to be greater than ", my dx * (predictionOrder + 1), U"Please increase the analysis window duration or lower the prediction order.");
+	}
 	// Convenience: analyse the whole sound into one LPC_frame
 	if (windowDuration > my dx * my nx) {
 		windowDuration = my dx * my nx;
@@ -416,9 +417,8 @@ static autoLPC _Sound_to_LPC (Sound me, int predictionOrder, double analysisWidt
 				frameErrorCount++;
 			}
 		}
-		if ( (i % 10) == 1) {
-			Melder_progress ( (double) i / nFrames, U"LPC analysis of frame ",
-			                   i, U" out of ", nFrames, U".");
+		if ((i % 10) == 1) {
+			Melder_progress ( (double) i / nFrames, U"LPC analysis of frame ", i, U" out of ", nFrames, U".");
 		}
 	}
 	return thee;
diff --git a/LPC/manual_LPC.cpp b/LPC/manual_LPC.cpp
index c882930..d457296 100644
--- a/LPC/manual_LPC.cpp
+++ b/LPC/manual_LPC.cpp
@@ -664,12 +664,16 @@ NORMAL (U"J.D. Markel & A.H. Gray, Jr. (1976): %%Linear Prediction of Speech.% "
 	"Springer Verlag, Berlin.")
 MAN_END
 
-
 MAN_BEGIN (U"Marple (1980)", U"djmw", 19980114)
 NORMAL (U"L. Marple (1980): \"A new autoregressive spectrum analysis algorithm.\" "
 	"%%IEEE Trans. on ASSP% #28, 441\\--454.")
 MAN_END
 
+MAN_BEGIN (U"Rothweiler (1999)", U"djmw", 20160507)
+NORMAL (U"J. Rothweiler (1999): \"On Polynomial Reduction in the Computation of LSP Frequencies.\" "
+	"%%IEEE Trans. on ASSP% #7, 592\\--594.")
+MAN_END
+
 MAN_BEGIN (U"Theil (1950)", U"djmw", 20121118)
 NORMAL (U"H. Theil (1950): \"A rank-invariant method of linear and polynomial regression analysis\", "
 	"%%Proceedings of Koninklijke Nederlandse Akademie van Wetenschappen% ##A.53#: 1397\\--1412.")
diff --git a/LPC/praat_LPC_init.cpp b/LPC/praat_LPC_init.cpp
index 7857e1d..7a8ae91 100644
--- a/LPC/praat_LPC_init.cpp
+++ b/LPC/praat_LPC_init.cpp
@@ -40,6 +40,7 @@
 #include "LPC_and_Cepstrumc.h"
 #include "LPC_and_Formant.h"
 #include "LPC_and_LFCC.h"
+#include "LPC_and_LineSpectralFrequencies.h"
 #include "LPC_and_Polynomial.h"
 #include "LPC_and_Tube.h"
 #include "LPC_to_Spectrogram.h"
@@ -736,6 +737,34 @@ DO
 	}
 END
 
+/********************LineSpectralFrequencies ********************************************/
+
+DIRECT (LineSpectralFrequencies_help) Melder_help (U"LineSpectralFrequencies"); END
+
+FORM (LineSpectralFrequencies_drawFrequencies, U"LineSpectralFrequencies: Draw frequencies", nullptr)
+	REAL (U"left Time range (s)", U"0.0")
+	REAL (U"right Time range (s)", U"0.0 (=all)")
+	REAL (U"left Frequency range (Hz)", U"0.0")
+	REAL (U"right Frequency range (Hz)", U"5000.0")
+	BOOLEAN (U"Garnish", true)
+	OK
+DO
+	autoPraatPicture picture;
+	LOOP {
+		iam (LineSpectralFrequencies);
+		LineSpectralFrequencies_drawFrequencies (me, GRAPHICS, GET_REAL (U"left Time range"), GET_REAL (U"right Time range"), 
+			GET_REAL (U"left Frequency range"), GET_REAL (U"right Frequency range"), GET_INTEGER (U"Garnish"));
+	}
+END
+
+DIRECT (LineSpectralFrequencies_to_LPC)
+	LOOP {
+		iam (LineSpectralFrequencies);
+		autoLPC thee = LineSpectralFrequencies_to_LPC (me);
+		praat_new (thee.move(), my name);
+	}
+END
+
 /********************LPC ********************************************/
 
 DIRECT (LPC_help) Melder_help (U"LPC"); END
@@ -820,6 +849,17 @@ DO
 	}
 END
 
+FORM (LPC_to_LineSpectralFrequencies, U"LPC: To LineSpectralFrequencies", nullptr)
+	REAL (U"Grid size", U"0.0")
+	OK
+DO
+	LOOP {
+		iam (LPC);
+		autoLineSpectralFrequencies thee = LPC_to_LineSpectralFrequencies (me, GET_REAL (U"Grid size"));
+		praat_new (thee.move(), my name);
+	}
+END
+
 FORM (LPC_to_Polynomial, U"LPC: To Polynomial", U"LPC: To Polynomial (slice)...")
 	REAL (U"Time (seconds)", U"0.0")
 	OK
@@ -1228,7 +1268,7 @@ extern void praat_TimeTier_query_init (ClassInfo klas);
 extern void praat_TimeTier_modify_init (ClassInfo klas);
 void praat_uvafon_LPC_init ();
 void praat_uvafon_LPC_init () {
-	Thing_recognizeClassesByName (classCepstrumc, classPowerCepstrum, classCepstrogram, classPowerCepstrogram, classLPC, classLFCC, classMFCC, classVocalTractTier, nullptr);
+	Thing_recognizeClassesByName (classCepstrumc, classPowerCepstrum, classCepstrogram, classPowerCepstrogram, classLPC, classLFCC, classLineSpectralFrequencies, classMFCC, classVocalTractTier, nullptr);
 
 	praat_addAction1 (classPowerCepstrum, 0, U"PowerCepstrum help", 0, 0, DO_PowerCepstrum_help);
 	praat_addAction1 (classPowerCepstrum, 0, U"Draw...", 0, 0, DO_PowerCepstrum_draw);
@@ -1294,6 +1334,10 @@ void praat_uvafon_LPC_init () {
 	praat_CC_init (classLFCC);
 	praat_addAction1 (classLFCC, 0, U"To LPC...", 0, 0, DO_LFCC_to_LPC);
 
+	praat_addAction1 (classLineSpectralFrequencies, 0, U"LineSpectralFrequencies help", 0, 0, DO_LineSpectralFrequencies_help);
+	praat_addAction1 (classLineSpectralFrequencies, 0, U"Draw frequencies...", 0, 0, DO_LineSpectralFrequencies_drawFrequencies);
+	praat_addAction1 (classLineSpectralFrequencies, 0, U"To LPC", 0, 0, DO_LineSpectralFrequencies_to_LPC);
+	
 	praat_addAction1 (classLPC, 0, U"LPC help", 0, 0, DO_LPC_help);
 	praat_addAction1 (classLPC, 0, DRAW_BUTTON, 0, 0, 0);
 	praat_addAction1 (classLPC, 0, U"Draw gain...", 0, 1, DO_LPC_drawGain);
@@ -1318,6 +1362,7 @@ void praat_uvafon_LPC_init () {
 	praat_addAction1 (classLPC, 0, U"To Formant (keep all)", 0, 0, DO_LPC_to_Formant_keep_all);
 	praat_addAction1 (classLPC, 0, U"To LFCC...", 0, 0, DO_LPC_to_LFCC);
 	praat_addAction1 (classLPC, 0, U"To Spectrogram...", 0, 0, DO_LPC_to_Spectrogram);
+	praat_addAction1 (classLPC, 0, U"To LineSpectralFrequencies...", 0, 0, DO_LPC_to_LineSpectralFrequencies);
 
 	praat_addAction2 (classLPC, 1, classSound, 1, U"Analyse", 0, 0, 0);
 	praat_addAction2 (classLPC, 1, classSound, 1, U"Filter...", 0, 0, DO_LPC_and_Sound_filter);
diff --git a/contrib/ola/FeatureWeights.cpp b/contrib/ola/FeatureWeights.cpp
index f6dde56..3590362 100644
--- a/contrib/ola/FeatureWeights.cpp
+++ b/contrib/ola/FeatureWeights.cpp
@@ -130,7 +130,7 @@ autoFeatureWeights FeatureWeights_compute           // Obsolete
     // Parameters                //
     ///////////////////////////////
 
-    Pattern pp,     // Source pattern
+    PatternList pp,     // Source pattern
                     //
     Categories c,   // Source categories
                     //
@@ -257,7 +257,7 @@ autoFeatureWeights FeatureWeights_computeWrapperExt
 
     KNN nn,         // Classifier
                     //
-    Pattern pp,     // test pattern
+    PatternList pp,     // test pattern
                     //
     Categories c,   // test categories
                     //
@@ -366,7 +366,7 @@ double FeatureWeights_evaluate      // Obsolete - use *_EvaluateWithTestSet
                                     //
     KNN nn,                         // Classifier
                                     //
-    Pattern pp,                     // test pattern
+    PatternList pp,                     // test pattern
                                     //
     Categories c,                   // test categories
                                     //
@@ -399,7 +399,7 @@ autoFeatureWeights FeatureWeights_computeRELIEF
     // Parameters                //
     ///////////////////////////////
 
-    Pattern pp,         // source pattern
+    PatternList pp,         // source pattern
                         //
     Categories c,       // source categories
                         //
@@ -408,7 +408,7 @@ autoFeatureWeights FeatureWeights_computeRELIEF
 )
 
 {
-	autoPattern p = Data_copy (pp);
+	autoPatternList p = Data_copy (pp);
 	autoFeatureWeights me = FeatureWeights_create (p -> nx);
 
 	/////////////////////////////////
diff --git a/contrib/ola/FeatureWeights.h b/contrib/ola/FeatureWeights.h
index 3c903c6..6fb30f2 100644
--- a/contrib/ola/FeatureWeights.h
+++ b/contrib/ola/FeatureWeights.h
@@ -29,7 +29,7 @@
 
 #include "Data.h"
 #include "TableOfReal.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "Categories.h"
 
 /////////////////////////////////////////////////////
@@ -72,7 +72,7 @@ long FeatureWeights_computePriors
 // Compute feature weights (obsolete)
 autoFeatureWeights FeatureWeights_compute
 (
-    Pattern pp,             // Source pattern
+    PatternList pp,             // Source pattern
     Categories c,           // Source categories
     long k                  // k(!)
 );
@@ -80,7 +80,7 @@ autoFeatureWeights FeatureWeights_compute
 // Compute feature weights according to the RELIEF-F algorithm
 autoFeatureWeights FeatureWeights_computeRELIEF
 (
-    Pattern pp,             // source pattern
+    PatternList pp,             // source pattern
     Categories c,           // source categories
     long k                  // k(!)
 );
diff --git a/contrib/ola/KNN.cpp b/contrib/ola/KNN.cpp
index 00da46c..bf6f8b7 100644
--- a/contrib/ola/KNN.cpp
+++ b/contrib/ola/KNN.cpp
@@ -87,7 +87,7 @@ int KNN_learn
 
     KNN me,         // the classifier to be trained
                     //
-    Pattern p,      // source pattern
+    PatternList p,      // source pattern
                     //
     Categories c,   // target categories
                     //
@@ -123,8 +123,8 @@ int KNN_learn
 				/*
 				 * Create without change.
 				 */
-				autoMatrix matrix = Matrix_appendRows (my input.get(), p, classPattern);
-                autoPattern tinput = matrix.static_cast_move <structPattern>();
+				autoMatrix matrix = Matrix_appendRows (my input.get(), p, classPatternList);
+                autoPatternList tinput = matrix.static_cast_move <structPatternList>();
 				autoCategories toutput = Data_copy (my output.get());
 				toutput -> merge (c);
 
@@ -162,7 +162,7 @@ int KNN_learn
 typedef struct
 {
         KNN me;
-        Pattern ps;
+        PatternList ps;
         long * output;
         FeatureWeights fws;
         long k;
@@ -180,7 +180,7 @@ autoCategories KNN_classifyToCategories
 
     KNN me,             // the classifier being used
                         //
-    Pattern ps,         // the pattern to classify
+    PatternList ps,         // the pattern to classify
                         //
     FeatureWeights fws, // feature weights
                         //
@@ -351,7 +351,7 @@ void * KNN_classifyToCategoriesAux
 
 typedef struct {
 	KNN me;
-	Pattern ps;
+	PatternList ps;
 	Categories uniqueCategories;
 	TableOfReal output;
 	FeatureWeights fws;
@@ -369,7 +369,7 @@ autoTableOfReal KNN_classifyToTableOfReal
 
     KNN me,             // the classifier being used
                         //
-    Pattern ps,         // source Pattern
+    PatternList ps,         // source PatternList
                         //
     FeatureWeights fws, // feature weights
                         //
@@ -555,7 +555,7 @@ autoCategories KNN_classifyFold
 
     KNN me,             // the classifier being used
                         //
-    Pattern ps,         // source Pattern
+    PatternList ps,         // source PatternList
                         //
     FeatureWeights fws, // feature weights
                         //
@@ -700,7 +700,7 @@ double KNN_evaluateWithTestSet
 
     KNN me,             // the classifier being used
                         //
-    Pattern p,          // The vectors of the test set
+    PatternList p,          // The vectors of the test set
                         //
     Categories c,       // The categories of the test set
                         //
@@ -815,9 +815,9 @@ double KNN_modelSearch
 
 double KNN_distanceEuclidean
 (
-    Pattern ps,         // Pattern 1
+    PatternList ps,         // PatternList 1
                         //
-    Pattern pt,         // Pattern 2
+    PatternList pt,         // PatternList 2
                         //
     FeatureWeights fws, // Feature weights
                         //
@@ -839,9 +839,9 @@ double KNN_distanceEuclidean
 
 double KNN_distanceManhattan
 (
-    Pattern ps,     // Pattern 1
+    PatternList ps,     // PatternList 1
                     //
-    Pattern pt,     // Pattern 2
+    PatternList pt,     // PatternList 2
                     //
     long rows,      // Vector index of pattern 1
                     //
@@ -887,9 +887,9 @@ long KNN_kNeighboursSkip
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,          // source pattern
+    PatternList j,          // source pattern
                         //
-    Pattern p,          // target pattern (where neighbours are sought for)
+    PatternList p,          // target pattern (where neighbours are sought for)
                         //
     FeatureWeights fws, // feature weights
                         //
@@ -957,9 +957,9 @@ long KNN_kNeighboursSkipRange
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,          // source-pattern (where the unknown is located)
+    PatternList j,          // source-pattern (where the unknown is located)
                         //
-    Pattern p,          // target pattern (where neighbours are sought for)
+    PatternList p,          // target pattern (where neighbours are sought for)
                         //
     FeatureWeights fws, // feature weights
                         //
@@ -1042,9 +1042,9 @@ long KNN_kNeighbours
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,          // source-pattern (where the unknown is located)
+    PatternList j,          // source-pattern (where the unknown is located)
                         //
-    Pattern p,          // target pattern (where neighbours are sought for)
+    PatternList p,          // target pattern (where neighbours are sought for)
                         //
     FeatureWeights fws, // feature weights
                         //
@@ -1118,9 +1118,9 @@ long KNN_kFriends
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,          // source-pattern
+    PatternList j,          // source-pattern
                         //
-    Pattern p,          // target pattern (where friends are sought for)
+    PatternList p,          // target pattern (where friends are sought for)
                         //
     Categories c,       // categories
                         //
@@ -1183,9 +1183,9 @@ double KNN_nearestEnemy
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,      // source-pattern
+    PatternList j,      // source-pattern
                     //
-    Pattern p,      // target pattern (where friends are sought for)
+    PatternList p,      // target pattern (where friends are sought for)
                     //
     Categories c,   // categories
                     //
@@ -1220,9 +1220,9 @@ long KNN_friendsAmongkNeighbours
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,      // source-pattern
+    PatternList j,      // source-pattern
                     //
-    Pattern p,      // target pattern (where friends are sought for)
+    PatternList p,      // target pattern (where friends are sought for)
                     //
     Categories c,   // categories
                     //
@@ -1259,9 +1259,9 @@ long KNN_kUniqueEnemies
     // Parameters                //
     ///////////////////////////////
 
-    Pattern j,      // source-pattern
+    PatternList j,      // source-pattern
                     //
-    Pattern p,      // target pattern (where friends are sought for)
+    PatternList p,      // target pattern (where friends are sought for)
                     //
     Categories c,   // categories
                     //
@@ -1339,7 +1339,7 @@ autoDissimilarity KNN_patternToDissimilarity
     // Parameters                //
     ///////////////////////////////
 
-    Pattern p,          // Pattern
+    PatternList p,          // PatternList
                         //
     FeatureWeights fws  // Feature weights
                         //
@@ -1466,7 +1466,7 @@ void KNN_removeInstance
 
     Melder_assert (y > 0 && y <= my nInstances);
 
-    autoPattern newPattern = Pattern_create (my nInstances - 1, my input -> nx);
+    autoPatternList newPattern = PatternList_create (my nInstances - 1, my input -> nx);
 	long yt = 1;
 	for (long cy = 1; cy <= my nInstances; cy ++) {
 		if (cy != y) {
@@ -1498,7 +1498,7 @@ void KNN_shuffleInstances
     if (my nInstances < 2) 
         return;   // it takes at least two to tango
 
-    autoPattern new_input = Pattern_create (my nInstances, my input -> nx);
+    autoPatternList new_input = PatternList_create (my nInstances, my input -> nx);
     autoCategories new_output = Categories_create ();
 	long y = 1;
 	while (my nInstances)
@@ -1715,7 +1715,7 @@ void * KNN_SA_t_copy_construct
 
 KNN_SA_t * KNN_SA_t_create
 (
-    Pattern p
+    PatternList p
 )
 
 {
@@ -1746,7 +1746,7 @@ void KNN_SA_partition
     // Parameters                //
     ///////////////////////////////
 
-    Pattern p,              // 
+    PatternList p,              // 
                             //
     long i1,                // i1 < i2
                             //
diff --git a/contrib/ola/KNN.h b/contrib/ola/KNN.h
index 9c83eae..01fd3eb 100644
--- a/contrib/ola/KNN.h
+++ b/contrib/ola/KNN.h
@@ -28,7 +28,7 @@
 /////////////////////////////////////////////////////
 
 #include "Data.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "Categories.h"
 #include "TableOfReal.h"
 #include "Permutation.h"
@@ -88,7 +88,7 @@ autoKNN KNN_create ();
 int KNN_learn
 (
     KNN me,             // the classifier to be trained
-    Pattern p,          // source pattern
+    PatternList p,          // source pattern
     Categories c,       // target categories
     int method,         // method <- REPLACE or APPEND
     int ordering        // ordering <- SHUFFLE?
@@ -98,7 +98,7 @@ int KNN_learn
 autoCategories KNN_classifyToCategories
 (
     KNN me,             // the classifier being used
-    Pattern ps,         // target pattern (where neighbours are sought for)
+    PatternList ps,         // target pattern (where neighbours are sought for)
     FeatureWeights fws, // feature weights
     long k,             // the number of sought after neighbours
     int dist            // distance weighting
@@ -113,11 +113,7 @@ void * KNN_classifyToCategoriesAux
 // Classification - To TableOfReal
 autoTableOfReal KNN_classifyToTableOfReal
 (
-    KNN me,             // the classifier being used
-    Pattern ps,         // target pattern (where neighbours are sought for)
-    FeatureWeights fws, // feature weights
-    long k,             // the number of sought after neighbours
-    int dist            // distance weighting
+    KNN me, PatternList ps, FeatureWeights fws, long int k, int dist
 );
 
 // Classification - To TableOfReal, threading aux
@@ -130,7 +126,7 @@ void * KNN_classifyToTableOfRealAux
 autoTableOfReal KNN_classifyToTableOfRealAll
 (
     KNN me,             // the classifier being used
-    Pattern ps,         // target pattern (where neighbours are sought for)
+    PatternList ps,         // target pattern (where neighbours are sought for)
     FeatureWeights fws, // feature weights
     long k,             // the number of sought after neighbours
     int dist            // distance weighting
@@ -140,7 +136,7 @@ autoTableOfReal KNN_classifyToTableOfRealAll
 autoCategories KNN_classifyFold
 (
     KNN me,             // the classifier being used
-    Pattern ps,         // target pattern (where neighbours are sought for)
+    PatternList ps,         // target pattern (where neighbours are sought for)
     FeatureWeights fws, // feature weights
     long k,             // the number of sought after neighbours
     int dist,           // distance weighting
@@ -162,7 +158,7 @@ double KNN_evaluate
 double KNN_evaluateWithTestSet
 (
     KNN me,             // the classifier being used
-    Pattern p,          // The vectors of the test set
+    PatternList p,          // The vectors of the test set
     Categories c,       // The categories of the test set
     FeatureWeights fws, // feature weights
     long k,             // the number of sought after neighbours
@@ -184,20 +180,13 @@ double KNN_modelSearch
 // Euclidean distance
 double KNN_distanceEuclidean
 (
-    Pattern ps,         // Pattern 1
-    Pattern pt,         // Pattern 2
-    FeatureWeights fws, // Feature weights
-    long rows,          // Vector index of pattern 1
-    long rowt           // Vector index of pattern 2
+    PatternList ps, PatternList pt, FeatureWeights fws, long int rows, long int rowt
 );
 
 // Manhattan distance
 double KNN_distanceManhattan
 (
-    Pattern ps,         // Pattern 1
-    Pattern pt,         // Pattern 2
-    long rows,          // Vector index of pattern 1
-    long rowt           // Vector index of pattern 2
+    PatternList ps, PatternList pt, long int rows, long int rowt
 );
 
 // Find longest distance
@@ -210,33 +199,14 @@ long KNN_max
 // Locate k neighbours, skip one + disposal of distance
 long KNN_kNeighboursSkip
 (
-    Pattern j,          // source pattern
-    Pattern p,          // target pattern (where neighbours are sought for)
-    FeatureWeights fws, // feature weights
-    long jy,            // source instance index
-    long k,             // the number of sought after neighbours
-    long * indices,     // memory space to contain the indices of
-    long skipper        // the index of the instance to be skipped
+    PatternList j, PatternList p, FeatureWeights fws, long int jy, long int k, long int* indices, long int skipper
 );
 
 // Locate the k nearest neighbours, exclude instances within the range defined
 // by [begin ... end]
 long KNN_kNeighboursSkipRange
 (
-    Pattern j,          // source-pattern (where the unknown is located)
-    Pattern p,          // target pattern (where neighbours are sought for)
-    FeatureWeights fws, // feature weights
-    long jy,            // the index of the unknown instance in the source pattern
-    long k,             // the number of sought after neighbours
-    long * indices,     // a pointer to a memory-space big enough for k longs
-                        // representing indices to the k neighbours in the
-                        // target pattern
-    double * distances, // a pointer to a memory-space big enough for k
-                        // doubles representing the distances to the k
-                        // neighbours
-    long begin,         // an index indicating the first instance in the
-                        // target pattern to be excluded from the search
-    long end            // an index indicating the last instance in the
+    PatternList j, PatternList p, FeatureWeights fws, long int jy, long int k, long int* indices, double* distances, long int begin, long int end
                         // range of excluded instances in the target
                         // pattern
 );
@@ -244,8 +214,8 @@ long KNN_kNeighboursSkipRange
 // Locate k neighbours
 long KNN_kNeighbours
 (
-    Pattern j,          // source-pattern (where the unknown is located)
-    Pattern p,          // target pattern (where neighbours are sought for)
+    PatternList j,          // source-pattern (where the unknown is located)
+    PatternList p,          // target pattern (where neighbours are sought for)
     FeatureWeights fws, // feature weights
     long jy,            // the index of the unknown instance in the source pattern
     long k,             // the number of sought after neighbours
@@ -260,12 +230,7 @@ long KNN_kNeighbours
 // Locating k (nearest) friends
 long KNN_kFriends
 (
-    Pattern j,          // source-pattern
-    Pattern p,          // target pattern (where friends are sought for)
-    Categories c,       // categories
-    long jy,            // the index of the source instance
-    long k,             // the number of sought after friends
-    long * indices      // a pointer to a memory-space big enough for k longs
+    PatternList j, PatternList p, Categories c, long int jy, long int k, long int* indices
                         // representing indices to the k friends in the
                         // target pattern
 );
@@ -273,38 +238,26 @@ long KNN_kFriends
 // Computing the distance to the nearest enemy
 double KNN_nearestEnemy
 (
-    Pattern j,          // source-pattern
-    Pattern p,          // target pattern (where friends are sought for)
-    Categories c,       // categories
-    long jy             // the index of the source instance
+    PatternList j, PatternList p, Categories c, long int jy
 );
 
 // Computing the number of friends among k neighbours
 long KNN_friendsAmongkNeighbours
 (
-    Pattern j,          // source-pattern
-    Pattern p,          // target pattern (where friends are sought for)
-    Categories c,       // categories
-    long jy,            // the index of the source instance
-    long k              // k (!)
+    PatternList j, PatternList p, Categories c, long int jy, long int k
 );
 
 // Locating k unique (nearest) enemies
 long KNN_kUniqueEnemies
 (
-    Pattern j,          // source-pattern
-    Pattern p,          // target pattern (where friends are sought for)
-    Categories c,       // categories
-    long jy,            // the index of the source instance
-    long k,             // k (!)
-    long * indices      // a memory space to hold the indices of the
+    PatternList j, PatternList p, Categories c, long int jy, long int k, long int* indices
                         // located enemies
 );
 
 // Compute dissimilarity matrix
 autoDissimilarity KNN_patternToDissimilarity
 (
-    Pattern p,          // Pattern
+    PatternList p,          // PatternList
     FeatureWeights fws  // Feature weights
 );
 
@@ -359,7 +312,7 @@ autoPermutation KNN_SA_ToPermutation
 // Experimental code
 typedef struct
 {
-    Pattern p;
+    PatternList p;
     long * indices;
 } KNN_SA_t;
     
@@ -406,7 +359,7 @@ void * KNN_SA_t_copy_construct
 // Experimental code
 KNN_SA_t * KNN_SA_t_create
 (
-    Pattern p
+    PatternList p
 );
 
 // Experimental code
@@ -418,7 +371,7 @@ void KNN_SA_t_destroy
 // Experimental code
 void KNN_SA_partition
 (
-    Pattern p,               
+    PatternList p,               
     long i1,                 
     long i2,                
     long * result           
@@ -441,7 +394,7 @@ autoFeatureWeights FeatureWeights_computeWrapperInt
 autoFeatureWeights FeatureWeights_computeWrapperExt
 (
     KNN nn,                 // Classifier
-    Pattern pp,             // test pattern
+    PatternList pp,             // test pattern
     Categories c,           // test categories
     long k,                 // k(!)
     int d,                  // distance weighting
@@ -456,7 +409,7 @@ double FeatureWeights_evaluate
 (
     FeatureWeights fws,     // Weights to evaluate
     KNN nn,                 // Classifier
-    Pattern pp,             // test pattern
+    PatternList pp,             // test pattern
     Categories c,           // test categories
     long k,                 // k(!)
     int d                   // distance weighting
diff --git a/contrib/ola/KNN_def.h b/contrib/ola/KNN_def.h
index 2187052..5602cba 100644
--- a/contrib/ola/KNN_def.h
+++ b/contrib/ola/KNN_def.h
@@ -21,7 +21,7 @@
 oo_DEFINE_CLASS (KNN, Daata)
 
 	oo_LONG (nInstances)
-	oo_AUTO_OBJECT (Pattern, 2, input)
+	oo_AUTO_OBJECT (PatternList, 2, input)
 	oo_AUTO_OBJECT (Categories, 0, output)
 
 	#if oo_DECLARING
diff --git a/contrib/ola/KNN_prune.cpp b/contrib/ola/KNN_prune.cpp
index ff39d93..c5d6629 100644
--- a/contrib/ola/KNN_prune.cpp
+++ b/contrib/ola/KNN_prune.cpp
@@ -80,7 +80,7 @@ long KNN_prune_prune
 
 void KNN_prune_sort
 (
-    Pattern p,      // source
+    PatternList p,      // source
     Categories c,   // source
     long k,         // k(!)
     long * indices, // indices of instances to be sorted
@@ -117,7 +117,7 @@ void KNN_prune_sort
 
 long KNN_prune_kCoverage
 (
-    Pattern p,      // source
+    PatternList p,      // source
     Categories c,   // source
     long y,         // source instance index
     long k,         // k(!)
@@ -150,7 +150,7 @@ long KNN_prune_kCoverage
 
 int KNN_prune_superfluous
 (
-    Pattern p,      // source
+    PatternList p,      // source
     Categories c,   // source
     long y,         // source instance index
     long k,         // k(!)
@@ -178,7 +178,7 @@ int KNN_prune_superfluous
 
 int KNN_prune_critical
 (
-    Pattern p,      // source
+    PatternList p,      // source
     Categories c,   // source
     long y,         // source instance index
     long k          // k(!)
@@ -204,7 +204,7 @@ int KNN_prune_critical
 
 int KNN_prune_noisy
 (
-    Pattern p,      // source
+    PatternList p,      // source
     Categories c,   // source
     long y,         // source instance index
     long k          // k(!)
diff --git a/contrib/ola/KNN_prune.h b/contrib/ola/KNN_prune.h
index 258af56..668c7d0 100644
--- a/contrib/ola/KNN_prune.h
+++ b/contrib/ola/KNN_prune.h
@@ -48,7 +48,7 @@ long KNN_prune_prune
 // sort indices according to pruning order defined by rule 2
 void KNN_prune_sort
 (
-    Pattern p,          // source
+    PatternList p,          // source
     Categories c,       // source
     long k,             // k(!)
     long * indices,     // indices of instances to be sorted
@@ -58,7 +58,7 @@ void KNN_prune_sort
 // k-coverage
 long KNN_prune_kCoverage
 (
-    Pattern p,          // source
+    PatternList p,          // source
     Categories c,       // source
     long y,             // source instance index
     long k,             // k(!)
@@ -68,7 +68,7 @@ long KNN_prune_kCoverage
 // testing for superfluousness
 int KNN_prune_superfluous
 (
-    Pattern p,          // source
+    PatternList p,          // source
     Categories c,       // source
     long y,             // source instance index
     long k,             // k(!)
@@ -78,7 +78,7 @@ int KNN_prune_superfluous
 // testing for criticalness
 int KNN_prune_critical
 (
-    Pattern p,          // source
+    PatternList p,          // source
     Categories c,       // source
     long y,             // source instance index
     long k              // k(!)
@@ -87,7 +87,7 @@ int KNN_prune_critical
 // testing for noisyness
 int KNN_prune_noisy
 (
-    Pattern p,          // source
+    PatternList p,          // source
     Categories c,       // source
     long y,             // source instance index
     long k              // k(!)
diff --git a/contrib/ola/Pattern_to_Categories_cluster.cpp b/contrib/ola/Pattern_to_Categories_cluster.cpp
index 1a21d10..b4518cf 100644
--- a/contrib/ola/Pattern_to_Categories_cluster.cpp
+++ b/contrib/ola/Pattern_to_Categories_cluster.cpp
@@ -32,13 +32,13 @@
 // Pattern_to_Categories_cluster                                                                              //
 /////////////////////////////////////////////////////////////////////////////////////////////
 
-autoCategories Pattern_to_Categories_cluster
+autoCategories PatternList_to_Categories_cluster
 (
     ///////////////////////////////
     // Parameters                //
     ///////////////////////////////
 
-    Pattern p,              // source
+    PatternList p,              // source
                             //
     FeatureWeights fws,     // feature weights
                             //
@@ -64,7 +64,7 @@ autoCategories Pattern_to_Categories_cluster
 		autoNUMvector <double> sizes (0L, k);
 		autoNUMvector <long> seeds (0L, k);
 
-		autoPattern centroids = Pattern_create (k, p -> nx);
+		autoPatternList centroids = PatternList_create (k, p -> nx);
 		autoNUMvector <double> beta (0L, centroids -> nx);
 
 		do
diff --git a/contrib/ola/Pattern_to_Categories_cluster.h b/contrib/ola/Pattern_to_Categories_cluster.h
index c3266e6..f5930f5 100644
--- a/contrib/ola/Pattern_to_Categories_cluster.h
+++ b/contrib/ola/Pattern_to_Categories_cluster.h
@@ -31,7 +31,7 @@
 // Praat datatypes                                 //
 /////////////////////////////////////////////////////
 
-#include "Pattern.h"
+#include "PatternList.h"
 #include "Categories.h"
 
 /////////////////////////////////////////////////////
@@ -44,14 +44,14 @@
 // Prototypes                                      //
 /////////////////////////////////////////////////////
 
-// Pattern_to_Categories_cluster                                                                            
-autoCategories Pattern_to_Categories_cluster
+// PatternList_to_Categories_cluster                                                                            
+autoCategories PatternList_to_Categories_cluster
 (
-    Pattern p,              // source
+    PatternList p,              // source
     FeatureWeights fws,     // feature weights
     long k,                 // k(!)
     double s,               // clustersize constraint 0 < s <= 1
     long m                  // reseed maximum
 );
 
-/* End of file Pattern_to_Categories_cluster.h */
+/* End of file PatternList_to_Categories_cluster.h */
diff --git a/contrib/ola/manual_KNN.cpp b/contrib/ola/manual_KNN.cpp
index ba7c038..f22ad3c 100644
--- a/contrib/ola/manual_KNN.cpp
+++ b/contrib/ola/manual_KNN.cpp
@@ -51,7 +51,7 @@ MAN_END
 
 MAN_BEGIN (U"FeatureWeights", U"Ola Söder", 20080729)
 INTRO (U"One of the @@types of objects@ in Praat.")
-NORMAL (U"A @FeatureWeights object is a %d-dimensional vector containing weight values used to transform a %d-dimensional space. Feature weighting can be used to improve the classifcation accuracy of @KNN classifiers. It can also be used to generate a @Dissimilarity matrix from a @Pattern object. @Dissimilarity matrices in conjunction with @@Multidimensional scaling|MDS-analysis@ can aid the visualization of high-dimensional data.")
+NORMAL (U"A @FeatureWeights object is a %d-dimensional vector containing weight values used to transform a %d-dimensional space. Feature weighting can be used to improve the classifcation accuracy of @KNN classifiers. It can also be used to generate a @Dissimilarity matrix from a @PatternList object. @Dissimilarity matrices in conjunction with @@Multidimensional scaling|MDS-analysis@ can aid the visualization of high-dimensional data.")
 MAN_END
 
 MAN_BEGIN (U"kNN classifiers 1. What is a kNN classifier?", U"Ola Söder", 20080529)
@@ -148,7 +148,7 @@ MAN_END
 MAN_BEGIN (U"kNN classifiers 2. Quick start", U"Ola Söder", 20080809)
 ENTRY (U"An example: Learning the Iris data set")
 NORMAL (U"In the @@Feedforward neural networks|the feedforward neural networks tutorial@ a description of how the @FFNet classifier in Praat can be applied to @@iris data set|the Iris data set@ can be found.")
-NORMAL (U"The same data can be used to test the %%k%NN feature of Praat. To do so create an example data set using the @@Create iris example...@ command found in the ##Feedforward neural networks# submenu. The form prompting for network topology settings can be ignored by selecting OK. Select the newly created @Pattern and @Categories objects and click ##To KNN Classifier...#. A form prompting for a name of the classifier to be created will be shown. The ordering in which instances are t [...]
+NORMAL (U"The same data can be used to test the %%k%NN feature of Praat. To do so create an example data set using the @@Create iris example...@ command found in the ##Feedforward neural networks# submenu. The form prompting for network topology settings can be ignored by selecting OK. Select the newly created @PatternList and @Categories objects and click ##To KNN Classifier...#. A form prompting for a name of the classifier to be created will be shown. The ordering in which instances a [...]
 NORMAL (U"To estimate how well the classifier can be expected to classify new samples of Irises select ##Query -# \\=> ##Get accuracy estimate...#. A form prompting for %%k%NN parameter settings and evaluation method will be shown. Experiment with the parameter settings until satisfactory results are achieved. If everything worked out the estimate will likely end up somewhere in the range of 94-98 percent.")
 NORMAL (U"An alternative to manually experimenting with model parameters is to let the computer do the job. This is done be choosing the @KNN object and thereafter selecting ##Query -# \\=> ##Get optimized parameters...#. The form shown prompts for a selection of parameters controlling the search. The default values will in most cases, including this, be appropriate.")
 NORMAL (U"Another way of improving classification accuracy is to transform the instance space in which the individual instances, in this case Irises, are stored as to maximize the distance between instances of different classes and minimize the distance between instances of the same class. This can be done by means of feature weighting. To do so select the @KNN object and choose ##To FeatureWeights...#. Adjust the %%k%NN settings according to the ones found by the model search algorithm  [...]
@@ -166,12 +166,12 @@ NORMAL (U"Initially %k number of so called %centroids are chosen. A %centroid is
 MAN_END
 
 MAN_BEGIN (U"k-means clustering 2. Quick start", U"Ola Söder", 20080529)
-NORMAL (U"Clustering using the %%k%-means clustering algorithm in Praat is done by selecting a @Pattern and choosing ##To Categories...#. In the appearing requester the number of sought after clusters (unique categories) can be specified. The cluster size ratio constraint (%z) imposes a constraint on the output such that %cluster size(%x) / %cluster size(%y) > %z for all clusters %x and %y in the resulting set of clusters. Valid values of %z are 0 < %z <= 1 where values near 0 imposes pr [...]
+NORMAL (U"Clustering using the %%k%-means clustering algorithm in Praat is done by selecting a @PatternList and choosing ##To Categories...#. In the appearing requester the number of sought after clusters (unique categories) can be specified. The cluster size ratio constraint (%z) imposes a constraint on the output such that %cluster size(%x) / %cluster size(%y) > %z for all clusters %x and %y in the resulting set of clusters. Valid values of %z are 0 < %z <= 1 where values near 0 impose [...]
 MAN_END
 
-MAN_BEGIN (U"Pattern to Dissimilarity", U"Ola Söder", 20080529)
-NORMAL (U"A @Dissimilarity matrix can be used in conjunction with @@Multidimensional scaling|Multidimensional scaling@ to aid visualization of high-dimensional data. A @Dissimilarity object is a matrix of the distances, according to the chosen @@Euclidean distance|distance function@, between all the data points in the @Pattern object.")
-NORMAL (U"A @Dissimilarity object can be created by selecting a @Pattern object and choosing ##To Dissimilarity#. The dissimilarity matrix can also be computed using feature weights. This is done by selecting a @Pattern object, an @FeatureWeights object and choosing ##To Dissimilarity#.")
+MAN_BEGIN (U"PatternList to Dissimilarity", U"Ola Söder", 20080529)
+NORMAL (U"A @Dissimilarity matrix can be used in conjunction with @@Multidimensional scaling|Multidimensional scaling@ to aid visualization of high-dimensional data. A @Dissimilarity object is a matrix of the distances, according to the chosen @@Euclidean distance|distance function@, between all the data points in the @PatternList object.")
+NORMAL (U"A @Dissimilarity object can be created by selecting a @PatternList object and choosing ##To Dissimilarity#. The dissimilarity matrix can also be computed using feature weights. This is done by selecting a @PatternList object, an @FeatureWeights object and choosing ##To Dissimilarity#.")
 MAN_END
 
 MAN_BEGIN (U"Euclidean distance", U"Ola Söder", 20080529)
@@ -182,18 +182,18 @@ MAN_END
 MAN_BEGIN (U"kNN classifiers 3. Command overview", U"Ola Söder", 20080809 )
 INTRO (U"KNN commands")
 ENTRY (U"Creation:")
-LIST_ITEM (U"\\bu @@Pattern & Categories: To KNN classifier...@")
+LIST_ITEM (U"\\bu @@PatternList & Categories: To KNN classifier...@")
 LIST_ITEM (U"\\bu @@Create KNN...@")
 ENTRY (U"Learning:")
-LIST_ITEM (U"\\bu @@KNN & Pattern & Categories: Learn...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList & Categories: Learn...@")
 ENTRY (U"Classification:")
-LIST_ITEM (U"\\bu @@KNN & Pattern: To Categories...@")
-LIST_ITEM (U"\\bu @@KNN & Pattern: To TabelOfReal...@")
-LIST_ITEM (U"\\bu @@KNN & Pattern & FeatureWeights: To Categories...@")
-LIST_ITEM (U"\\bu @@KNN & Pattern & FeatureWeights: To TableOfReal...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList: To Categories...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList: To TabelOfReal...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList & FeatureWeights: To Categories...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList & FeatureWeights: To TableOfReal...@")
 ENTRY (U"Evaluation:")
-LIST_ITEM (U"\\bu @@KNN & Pattern & Categories: Evaluate...@")
-LIST_ITEM (U"\\bu @@KNN & Pattern & Categories & FeatureWeights: Evaluate...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList & Categories: Evaluate...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList & Categories & FeatureWeights: Evaluate...@")
 ENTRY (U"Queries")
 LIST_ITEM (U"\\bu @@KNN: Get optimized parameters...@")
 LIST_ITEM (U"\\bu @@KNN: Get accuracy estimate...@")
@@ -208,17 +208,17 @@ LIST_ITEM (U"\\bu @@KNN: Prune...@")
 LIST_ITEM (U"\\bu @@KNN: Reset...@")
 ENTRY (U"Miscellaneous:")
 LIST_ITEM (U"\\bu @@KNN: To FeatureWeights...@")
-LIST_ITEM (U"\\bu @@KNN & Pattern & Categories: To FeatureWeights...@") 
-LIST_ITEM (U"\\bu @@Pattern & Categories: To FeatureWeights...@")
+LIST_ITEM (U"\\bu @@KNN & PatternList & Categories: To FeatureWeights...@") 
+LIST_ITEM (U"\\bu @@PatternList & Categories: To FeatureWeights...@")
 ENTRY (U"Pre/post processing:")
-LIST_ITEM (U"\\bu @@Pattern: To Categories...@")
-LIST_ITEM (U"\\bu @@Pattern & FeatureWeights: To Categories...@")
-LIST_ITEM (U"\\bu @@Pattern: To Dissimilarity...@")
-LIST_ITEM (U"\\bu @@Pattern & FeatureWeights: To Dissimilarity...@")
+LIST_ITEM (U"\\bu @@PatternList: To Categories...@")
+LIST_ITEM (U"\\bu @@PatternList & FeatureWeights: To Categories...@")
+LIST_ITEM (U"\\bu @@PatternList: To Dissimilarity...@")
+LIST_ITEM (U"\\bu @@PatternList & FeatureWeights: To Dissimilarity...@")
 MAN_END
 
-MAN_BEGIN (U"Pattern & Categories: To KNN classifier...", U"Ola Söder", 20080726 )
-INTRO (U"Create and train a @KNN classifier using the selected @Pattern and @Categories objects as training data.")
+MAN_BEGIN (U"PatternList & Categories: To KNN classifier...", U"Ola Söder", 20080726 )
+INTRO (U"Create and train a @KNN classifier using the selected @PatternList and @Categories objects as training data.")
 ENTRY (U"Settings")
 TAG (U"##Name")\
 DEFINITION (U"The name of the @KNN classifier.")
@@ -236,8 +236,8 @@ ENTRY (U"See also:")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern & Categories: Learn...", U"Ola Söder", 20080726 )
-INTRO (U"Train the selected @KNN classifier using the chosen @Pattern and @Categories objects as training data.")
+MAN_BEGIN (U"KNN & PatternList & Categories: Learn...", U"Ola Söder", 20080726 )
+INTRO (U"Train the selected @KNN classifier using the chosen @PatternList and @Categories objects as training data.")
 ENTRY (U"Settings")
 TAG (U"##Name")
 DEFINITION (U"The name of the classifier.")
@@ -248,8 +248,8 @@ LIST_ITEM (U"@@kNN classifiers@")
 LIST_ITEM (U"@@kNN classifiers 1. What is a kNN classifier?@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern: To Categories...", U"Ola Söder", 20080726 )
-INTRO (U"Use the selected @KNN classifier to classify the chosen @Pattern. A @Categories object containing the result will be created.")
+MAN_BEGIN (U"KNN & PatternList: To Categories...", U"Ola Söder", 20080726 )
+INTRO (U"Use the selected @KNN classifier to classify the chosen @PatternList. A @Categories object containing the result will be created.")
 ENTRY (U"Settings")
 TAG (U"##k neighbours")
 DEFINITION (U"The size of the neighbourhood.")
@@ -260,8 +260,8 @@ LIST_ITEM (U"@@kNN classifiers@")
 LIST_ITEM (U"@@kNN classifiers 1. What is a kNN classifier?@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern: To TabelOfReal...", U"Ola Söder", 20080718 )
-INTRO (U"Use the selected @KNN classifier to classify the chosen @Pattern. A @TableOfReal object containing verbose information on the decision process  will be created.")
+MAN_BEGIN (U"KNN & PatternList: To TabelOfReal...", U"Ola Söder", 20080718 )
+INTRO (U"Use the selected @KNN classifier to classify the chosen @PatternList. A @TableOfReal object containing verbose information on the decision process  will be created.")
 ENTRY (U"Settings")
 TAG (U"##k neighbours")
 DEFINITION (U"The size of the neighbourhood.")
@@ -274,8 +274,8 @@ LIST_ITEM (U"@@kNN classifiers@")
 LIST_ITEM (U"@@kNN classifiers 1. What is a kNN classifier?@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern & FeatureWeights: To Categories...", U"Ola Söder", 20080726 )
-INTRO (U"Use the selected @KNN classifier and @FeatureWeights object to classify the chosen @Pattern. A @Categories object containing the result will be created.")
+MAN_BEGIN (U"KNN & PatternList & FeatureWeights: To Categories...", U"Ola Söder", 20080726 )
+INTRO (U"Use the selected @KNN classifier and @FeatureWeights object to classify the chosen @PatternList. A @Categories object containing the result will be created.")
 ENTRY (U"Settings")
 TAG (U"##k neighbours")
 DEFINITION (U"The size of the neighbourhood.")
@@ -290,8 +290,8 @@ LIST_ITEM (U"@@kNN classifiers 1. What is a kNN classifier?@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern & FeatureWeights: To TableOfReal...", U"Ola Söder", 20080718 )
-INTRO (U"Use the selected @KNN classifier and the feature weights, @FeatureWeights, to classify the chosen @Pattern. A @TableOfReal object containing verbose information on the decision process will be created.")
+MAN_BEGIN (U"KNN & PatternList & FeatureWeights: To TableOfReal...", U"Ola Söder", 20080718 )
+INTRO (U"Use the selected @KNN classifier and the feature weights, @FeatureWeights, to classify the chosen @PatternList. A @TableOfReal object containing verbose information on the decision process will be created.")
 ENTRY (U"Settings")
 TAG (U"##k neighbours")
 DEFINITION (U"The size of the neighbourhood.")
@@ -338,8 +338,8 @@ TAG (U"##Vote weighting")
 DEFINITION (U"The type of vote weighting to be used.")
 ENTRY (U"See also:")
 LIST_ITEM (U"@@KNN & FeatureWeights: Get accuracy estimate...@")
-LIST_ITEM (U"@@KNN & Pattern & Categories: Evaluate...@")
-LIST_ITEM (U"@@KNN & Pattern & Categories & FeatureWeights: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories & FeatureWeights: Evaluate...@")
 LIST_ITEM (U"@@kNN classifiers 1.1. Improving classification accuracy@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
 LIST_ITEM (U"@@kNN classifiers 1.1.2. Model selection@")
@@ -357,23 +357,23 @@ DEFINITION (U"The size of the neighbourhood.")
 TAG (U"##Vote weighting")
 DEFINITION (U"The type of vote weighting to be used.")
 ENTRY (U"See also:")
-LIST_ITEM (U"@@KNN & Pattern & Categories: Evaluate...@")
-LIST_ITEM (U"@@KNN & Pattern & Categories & FeatureWeights: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories & FeatureWeights: Evaluate...@")
 LIST_ITEM (U"@@kNN classifiers 1.1. Improving classification accuracy@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
 LIST_ITEM (U"@@kNN classifiers 1.1.2. Model selection@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END 
 
-MAN_BEGIN (U"KNN & Pattern & Categories: Evaluate...", U"Ola Söder", 20080718)
-INTRO (U"Estimate the classification accuracy of the chosen @KNN classifier using the selected @Pattern and @Categories objects as test set.")
+MAN_BEGIN (U"KNN & PatternList & Categories: Evaluate...", U"Ola Söder", 20080718)
+INTRO (U"Estimate the classification accuracy of the chosen @KNN classifier using the selected @PatternList and @Categories objects as test set.")
 ENTRY (U"Settings")
 TAG (U"##k neighbours")
 DEFINITION (U"The size of the neighbourhood.")
 TAG (U"##Vote weighting")
 DEFINITION (U"The type of vote weighting to be used.")
 ENTRY (U"See also:")
-LIST_ITEM (U"@@KNN & Pattern & Categories & FeatureWeights: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories & FeatureWeights: Evaluate...@")
 LIST_ITEM (U"@@KNN: Get accuracy estimate...@")
 LIST_ITEM (U"@@kNN classifiers 1.1. Improving classification accuracy@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
@@ -381,15 +381,15 @@ LIST_ITEM (U"@@kNN classifiers 1.1.2. Model selection@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern & Categories & FeatureWeights: Evaluate...", U"Ola Söder", 20080718)
-INTRO (U"Estimate the classification accuracy of the chosen @KNN classifier using the selected @Pattern and @Categories objects as test set. The selected @FeatureWeights object will be used in the classification process.")
+MAN_BEGIN (U"KNN & PatternList & Categories & FeatureWeights: Evaluate...", U"Ola Söder", 20080718)
+INTRO (U"Estimate the classification accuracy of the chosen @KNN classifier using the selected @PatternList and @Categories objects as test set. The selected @FeatureWeights object will be used in the classification process.")
 ENTRY (U"Settings")
 TAG (U"##k neighbours")
 DEFINITION (U"The size of the neighbourhood.")
 TAG (U"##Vote weighting")
 DEFINITION (U"The type of vote weighting to be used.")
 ENTRY (U"See also:")
-LIST_ITEM (U"@@KNN & Pattern & Categories: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories: Evaluate...@")
 LIST_ITEM (U"@@KNN: Get accuracy estimate...@")
 LIST_ITEM (U"@@kNN classifiers 1.1. Improving classification accuracy@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
@@ -404,7 +404,7 @@ LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
 MAN_BEGIN (U"KNN: Extract input Pattern", U"Ola Söder", 20080726)
-INTRO (U"Create a new @Pattern object identical to the one in the instance base of the selected @KNN classifier.")
+INTRO (U"Create a new @PatternList object identical to the one in the instance base of the selected @KNN classifier.")
 ENTRY (U"See also:")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
@@ -463,13 +463,13 @@ ENTRY (U"See also:")
 LIST_ITEM (U"@@kNN classifiers 1.1.1.2. Wrapper-based feature weighting@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
 LIST_ITEM (U"@@kNN classifiers 1.1. Improving classification accuracy@")
-LIST_ITEM (U"@@KNN & Pattern & Categories & FeatureWeights: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories & FeatureWeights: Evaluate...@")
 LIST_ITEM (U"@@kNN classifiers 1. What is a kNN classifier?@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"KNN & Pattern & Categories: To FeatureWeights...", U"Ola Söder", 20080809)
-INTRO (U"Wrap the selected @KNN and use its classification accuracy on the test set constituted by the @Pattern and @Categories objects as feedback to guide the search for the optimal feature weights. A @FeatureWeights object will be created.")
+MAN_BEGIN (U"KNN & PatternList & Categories: To FeatureWeights...", U"Ola Söder", 20080809)
+INTRO (U"Wrap the selected @KNN and use its classification accuracy on the test set constituted by the @PatternList and @Categories objects as feedback to guide the search for the optimal feature weights. A @FeatureWeights object will be created.")
 ENTRY (U"Settings")
 TAG (U"##Learning rate")
 DEFINITION (U"The rate at which the maximum distance between the pivot and a random seed is decremented.")
@@ -487,12 +487,12 @@ ENTRY (U"See also:")
 LIST_ITEM (U"@@kNN classifiers 1.1.1.2. Wrapper-based feature weighting@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
 LIST_ITEM (U"@@kNN classifiers 1.1. Improving classification accuracy@")
-LIST_ITEM (U"@@KNN & Pattern & Categories & FeatureWeights: Evaluate...@")
+LIST_ITEM (U"@@KNN & PatternList & Categories & FeatureWeights: Evaluate...@")
 LIST_ITEM (U"@@kNN classifiers 1. What is a kNN classifier?@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"Pattern & Categories: To FeatureWeights...", U"Ola Söder", 20080809) 
+MAN_BEGIN (U"PatternList & Categories: To FeatureWeights...", U"Ola Söder", 20080809) 
 INTRO (U"Compute an estimate of the optimal feature weights using the @@kNN classifiers 1.1.1.1. Filter-based feature weighting|RELIEF-F algorithm at .")
 ENTRY (U"Setting")
 TAG (U"##k neighbours")
@@ -503,8 +503,8 @@ LIST_ITEM (U"@@kNN classifiers 1.1.1.2. Wrapper-based feature weighting@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"Pattern: To Categories...", U"Ola Söder", 20080728)
-INTRO (U"Split the given @Pattern into a fixed number of clusters using the @@k-means clustering|%%k%-means clustering algorithm at . A @Categories object containing numbered categories corresponding to the generated clusters will be created.")
+MAN_BEGIN (U"PatternList: To Categories...", U"Ola Söder", 20080728)
+INTRO (U"Split the given @PatternList into a fixed number of clusters using the @@k-means clustering|%%k%-means clustering algorithm at . A @Categories object containing numbered categories corresponding to the generated clusters will be created.")
 ENTRY (U"Settings")
 TAG (U"##k clusters")
 DEFINITION (U"The number of clusters to be generated.")
@@ -516,8 +516,8 @@ ENTRY (U"See also:")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"Pattern & FeatureWeights: To Categories...", U"Ola Söder", 20080728)
-INTRO (U"Split the given @Pattern into a fixed number of clusters using the @@k-means clustering|%%k%-means clustering algorithm@ and the feature weights contained within the selected @FeatureWeights object. A @Categories object containing numbered categories corresponding to the generated clusters will be created.")
+MAN_BEGIN (U"PatternList & FeatureWeights: To Categories...", U"Ola Söder", 20080728)
+INTRO (U"Split the given @PatternList into a fixed number of clusters using the @@k-means clustering|%%k%-means clustering algorithm@ and the feature weights contained within the selected @FeatureWeights object. A @Categories object containing numbered categories corresponding to the generated clusters will be created.")
 ENTRY (U"Settings")
 TAG (U"##k clusters")
 DEFINITION (U"The number of clusters to be generated.")
@@ -530,15 +530,15 @@ LIST_ITEM (U"@@kNN classifiers@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
 MAN_END
 
-MAN_BEGIN (U"Pattern: To Dissimilarity...", U"Ola Söder", 20080718)
-INTRO (U"Generate a @Dissimilarity matrix from the selected @Pattern. Dissimilarities are computed using the @@Euclidean distance@")
+MAN_BEGIN (U"PatternList: To Dissimilarity...", U"Ola Söder", 20080718)
+INTRO (U"Generate a @Dissimilarity matrix from the selected @PatternList. Dissimilarities are computed using the @@Euclidean distance@")
 ENTRY (U"See also:")
 LIST_ITEM (U"@@Multidimensional scaling@")
 LIST_ITEM (U"@@kNN classifiers@")
 MAN_END
 
-MAN_BEGIN (U"Pattern & FeatureWeights: To Dissimilarity...", U"Ola Söder", 20080718)
-INTRO (U"Generate a @Dissimilarity matrix from the selected @Pattern using the feature weights contained within the selected @FeatureWeights object. Dissimilarities are computed using the @@Euclidean distance at .")
+MAN_BEGIN (U"PatternList & FeatureWeights: To Dissimilarity...", U"Ola Söder", 20080718)
+INTRO (U"Generate a @Dissimilarity matrix from the selected @PatternList using the feature weights contained within the selected @FeatureWeights object. Dissimilarities are computed using the @@Euclidean distance at .")
 ENTRY (U"See also:")
 LIST_ITEM (U"@@Multidimensional scaling@")
 LIST_ITEM (U"@@kNN classifiers 1.1.1. Feature weighting@")
diff --git a/contrib/ola/praat_contrib_Ola_KNN.cpp b/contrib/ola/praat_contrib_Ola_KNN.cpp
index 7acd241..bda1feb 100644
--- a/contrib/ola/praat_contrib_Ola_KNN.cpp
+++ b/contrib/ola/praat_contrib_Ola_KNN.cpp
@@ -54,7 +54,7 @@ FORM (KNN_Pattern_Categories_to_KNN, U"Create kNN classifier", U"kNN classifiers
 	RADIOBUTTON (U"Sequential")
 	OK2
 DO
-	iam_ONLY (Pattern);
+	iam_ONLY (PatternList);
 	thouart_ONLY (Categories);
 	int ordering = GET_INTEGER (U"Ordering");
 	autoKNN knn = KNN_create ();
@@ -68,9 +68,9 @@ DO
 	int result = KNN_learn (knn.get(), me, thee, kOla_REPLACE, ordering);
 	switch (result) {
 		case kOla_PATTERN_CATEGORIES_MISMATCH:
-			Melder_throw (U"The number of Categories should be equal to the number of rows in Pattern.");
+			Melder_throw (U"The number of Categories should be equal to the number of rows in PatternList.");
 		case kOla_DIMENSIONALITY_MISMATCH:
-			Melder_throw (U"The dimensionality of Pattern should be equal to that of the instance base.");
+			Melder_throw (U"The dimensionality of PatternList should be equal to that of the instance base.");
 		default:
 			praat_new (knn.move(), GET_STRING(U"Name"));
 	}
@@ -168,7 +168,7 @@ DO
 	autoFeatureWeights fws = FeatureWeights_create (my input -> nx);
 	double result = KNN_evaluate (me, fws.get(), k, vt, mode);
 	if (lround (result) == kOla_FWEIGHTS_MISMATCH)
-		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the Pattern.");
+		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the PatternList.");
 	Melder_information (100 * result, U" percent of the instances correctly classified.");   // BUG: use Melder_percent
 END2 }
 
@@ -213,7 +213,7 @@ DO
 	}
 	double result = KNN_evaluate (me, thee, k, vt, mode);
 	if (lround (result) == kOla_FWEIGHTS_MISMATCH)
-		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the Pattern.");
+		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the PatternList.");
 	Melder_information (100 * result, U" percent of the instances correctly classified.");
 END2 }
 
@@ -221,7 +221,7 @@ DIRECT2 (KNN_extractInputPatterns) {
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty.");
-	autoPattern input = Data_copy (my input.get());
+	autoPatternList input = Data_copy (my input.get());
 	praat_new (input.move(), U"Input Patterns");
 END2 }
 
@@ -286,7 +286,7 @@ FORM (KNN_learn, U"Learning", U"kNN classifiers 1. What is a kNN classifier?") {
 	OK2
 DO
 	iam_ONLY (KNN);
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	heis_ONLY (Categories);
 	int ordering = GET_INTEGER (U"Ordering");
 	switch (ordering) {
@@ -308,9 +308,9 @@ DO
 	}
 	switch (result) {
 		case kOla_PATTERN_CATEGORIES_MISMATCH:  
-			Melder_throw (U"The number of Categories should be equal to the number of rows in Pattern.");
+			Melder_throw (U"The number of Categories should be equal to the number of rows in PatternList.");
 		case kOla_DIMENSIONALITY_MISMATCH:
-			Melder_throw (U"The dimensionality of Pattern should be equal to that of the instance base.");
+			Melder_throw (U"The dimensionality of PatternList should be equal to that of the instance base.");
 	}
 END2 }
 
@@ -318,7 +318,7 @@ END2 }
 // Evaluation                                                                          //
 /////////////////////////////////////////////////////////////////////////////////////////
 
-FORM (KNN_evaluateWithTestSet, U"Evaluation", U"KNN & Pattern & Categories: Evaluate...") {
+FORM (KNN_evaluateWithTestSet, U"Evaluation", U"KNN & PatternList & Categories: Evaluate...") {
 	INTEGER (U"k neighbours", U"1")
 	RADIO (U"Vote weighting", 1)
 	RADIOBUTTON (U"Inversed squared distance")
@@ -329,7 +329,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty");
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	heis_ONLY (Categories);
 	long k = GET_INTEGER (U"k neighbours");
 	if (k < 1 || k > my nInstances)
@@ -347,15 +347,15 @@ DO
 			break;
 	}
 	if (thy ny != his size)
-		Melder_throw (U"The number of Categories should be equal to the number of rows in Pattern.");
+		Melder_throw (U"The number of Categories should be equal to the number of rows in PatternList.");
 	if (thy nx != (my input)->nx)
-		Melder_throw (U"The dimensionality of Pattern should be equal to that of the instance base.");
+		Melder_throw (U"The dimensionality of PatternList should be equal to that of the instance base.");
 	autoFeatureWeights fws = FeatureWeights_create (thy nx);
 	double result = KNN_evaluateWithTestSet (me, thee, him, fws.get(), k, vt);
 	Melder_information (100 * result, U" percent of the instances correctly classified.");
 END2 }
 
-FORM (KNN_evaluateWithTestSetAndFeatureWeights, U"Evaluation", U"KNN & Pattern & Categories & FeatureWeights: Evaluate...") {
+FORM (KNN_evaluateWithTestSetAndFeatureWeights, U"Evaluation", U"KNN & PatternList & Categories & FeatureWeights: Evaluate...") {
 	INTEGER (U"k neighbours", U"1")
 	RADIO (U"Vote weighting", 1)
 	RADIOBUTTON (U"Inversed squared distance")
@@ -366,7 +366,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty");
-	Pattern p = (Pattern) ONLY (classPattern);
+	PatternList p = (PatternList) ONLY (classPatternList);
 	Categories c = (Categories) ONLY (classCategories);
 	FeatureWeights fws = (FeatureWeights) ONLY (classFeatureWeights);
 	long k = GET_INTEGER (U"k neighbours");
@@ -385,11 +385,11 @@ DO
 			break;
 	}
 	if (p -> ny != c->size)
-		Melder_throw (U"The number of Categories should be equal to the number of rows in Pattern.");
+		Melder_throw (U"The number of Categories should be equal to the number of rows in PatternList.");
 	if (p -> nx != my input -> nx)
-		Melder_throw (U"The dimensionality of Pattern should be equal to that of the instance base.");
+		Melder_throw (U"The dimensionality of PatternList should be equal to that of the instance base.");
 	if (p->nx != fws -> fweights -> numberOfColumns)
-		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the Pattern.");
+		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the PatternList.");
 	double result = KNN_evaluateWithTestSet (me, p, c, fws, k, vt);
 	Melder_information (100 * result, U" percent of the instances correctly classified.");
 END2 }
@@ -398,7 +398,7 @@ END2 }
 // Classification                                                                      //
 /////////////////////////////////////////////////////////////////////////////////////////
 
-FORM (KNN_toCategories, U"Classification", U"KNN & Pattern: To Categories...") {
+FORM (KNN_toCategories, U"Classification", U"KNN & PatternList: To Categories...") {
 	INTEGER (U"k neighbours", U"1")
 	RADIO (U"Vote weighting", 1)
 	RADIOBUTTON (U"Inversed squared distance")
@@ -409,7 +409,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty.");
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	long k = GET_INTEGER (U"k neighbours");
 	if (k < 1 || k > my nInstances)
 		Melder_throw (U"Please select a value of k such that 0 < k < ", my nInstances + 1, U".");
@@ -426,13 +426,13 @@ DO
 			break;
 	}
 	if (thy nx != my input -> nx)
-		Melder_throw (U"The dimensionality of Pattern should match that of the instance base.");
+		Melder_throw (U"The dimensionality of PatternList should match that of the instance base.");
 	autoFeatureWeights fws = FeatureWeights_create (thy nx);
 	autoCategories result = KNN_classifyToCategories (me, thee, fws.get(), k, vt);
 	praat_new (result.move(), U"Output");
 END2 }
 
-FORM (KNN_toTableOfReal, U"Classification", U"KNN & Pattern: To TabelOfReal...") {
+FORM (KNN_toTableOfReal, U"Classification", U"KNN & PatternList: To TabelOfReal...") {
 	INTEGER (U"k neighbours", U"1")
 	RADIO (U"Vote weighting", 1)
 	RADIOBUTTON (U"Inversed squared distance")
@@ -443,7 +443,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty.");
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	long k = GET_INTEGER (U"k neighbours");
 	if (k < 1 || k > my nInstances)
 		Melder_throw (U"Please select a value of k such that 0 < k < ", my nInstances + 1, U".");
@@ -461,13 +461,13 @@ DO
 			break;
 	}
 	if (thy nx != my input -> nx)
-		Melder_throw (U"The dimensionality of Pattern should match that of the instance base.");
+		Melder_throw (U"The dimensionality of PatternList should match that of the instance base.");
 	autoTableOfReal result = KNN_classifyToTableOfReal (me, thee, fws.get(), k, vt);
 	praat_new (result.move(), U"Output");
 END2 }
 
-FORM (KNN_toCategoriesWithFeatureWeights, U"Classification", U"KNN & Pattern & FeatureWeights: To Categories...") {
-	INTEGER (U"k neighbours", U"KNN & Pattern & FeatureWeights: To Categories...")
+FORM (KNN_toCategoriesWithFeatureWeights, U"Classification", U"KNN & PatternList & FeatureWeights: To Categories...") {
+	INTEGER (U"k neighbours", U"KNN & PatternList & FeatureWeights: To Categories...")
 	RADIO (U"Vote weighting", 1)
 	RADIOBUTTON (U"Inversed squared distance")  
 	RADIOBUTTON (U"Inversed distance")
@@ -477,7 +477,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty.");
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	heis_ONLY (FeatureWeights);
 	int vt = GET_INTEGER (U"Vote weighting");
 	switch (vt) {
@@ -495,14 +495,14 @@ DO
 	if (k < 1 || k > my nInstances)
 		Melder_throw (U"Please select a value of k such that 0 < k < ", my nInstances + 1, U".");
 	if (thy nx != (my input)->nx)
-		Melder_throw (U"The dimensionality of Pattern should be equal to that of the instance base.");
+		Melder_throw (U"The dimensionality of PatternList should be equal to that of the instance base.");
 	if (thy nx != his fweights -> numberOfColumns)
-		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the Pattern.");
+		Melder_throw (U"The number of feature weights should be equal to the dimensionality of the PatternList.");
 	autoCategories result = KNN_classifyToCategories (me, thee, him, k, vt);
 	praat_new (result.move(), U"Output");
 END2 }
 
-FORM (KNN_toTableOfRealWithFeatureWeights, U"Classification", U"KNN & Pattern & FeatureWeights: To TableOfReal...") {
+FORM (KNN_toTableOfRealWithFeatureWeights, U"Classification", U"KNN & PatternList & FeatureWeights: To TableOfReal...") {
 	INTEGER (U"k neighbours", U"1")
 	RADIO (U"Vote weighting", 1)
 	RADIOBUTTON (U"Inversed squared distance")
@@ -513,7 +513,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty.");
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	heis_ONLY (FeatureWeights);
 	long k = GET_INTEGER (U"k neighbours");
 	int vt = GET_INTEGER (U"Vote weighting");
@@ -540,13 +540,13 @@ END2 }
 // Clustering                                                                          //
 /////////////////////////////////////////////////////////////////////////////////////////
 
-FORM (Pattern_to_Categories_cluster, U"k-means clustering", U"Pattern: To Categories...") {
+FORM (Pattern_to_Categories_cluster, U"k-means clustering", U"PatternList: To Categories...") {
 	INTEGER (U"k clusters", U"1")
 	POSITIVE (U"Cluster size ratio constraint", U"0.0000001");
 	INTEGER (U"Maximum number of reseeds", U"1000")
 	OK2
 DO
-	iam_ONLY (Pattern);
+	iam_ONLY (PatternList);
 	if (my nx > 0 && my ny > 0) {
 		long k = GET_INTEGER (U"k clusters");
 		if (k < 1 || k > my ny)
@@ -558,20 +558,20 @@ DO
 		if (rc > 1 || rc <= 0)
 			Melder_throw (U"Please select a value of the cluster size ratio constraint c such that 0 < c <= 1.");
 		autoFeatureWeights fws = FeatureWeights_create (my nx);
-		autoCategories result = Pattern_to_Categories_cluster (me, fws.get(), k, rc, rs);
+		autoCategories result = PatternList_to_Categories_cluster (me, fws.get(), k, rc, rs);
 		praat_new (result.move(), U"Output");
 	} else {
-		Melder_throw (U"Pattern is empty.");
+		Melder_throw (U"PatternList is empty.");
 	}
 END2 }
 
-FORM (Pattern_to_Categories_clusterWithFeatureWeights, U"k-means clustering", U"Pattern & FeatureWeights: To Categories...") {
+FORM (Pattern_to_Categories_clusterWithFeatureWeights, U"k-means clustering", U"PatternList & FeatureWeights: To Categories...") {
 	INTEGER (U"k clusters", U"1")
 	POSITIVE (U"Cluster size ratio constraint", U"0.0000001");
 	INTEGER (U"Maximum number of reseeds", U"1000")
 	OK2
 DO
-	iam_ONLY (Pattern);
+	iam_ONLY (PatternList);
 	if (my nx > 0 && my ny > 0) {
 		thouart_ONLY (FeatureWeights); 
 		if (my nx != thy fweights -> numberOfColumns)
@@ -585,10 +585,10 @@ DO
 		double rc =  GET_REAL(U"Cluster size ratio constraint");
 		if (rc > 1 || rc <= 0)
 			Melder_throw (U"Please select a value of the cluster size ratio constraint c such that 0 < c <= 1.");
-		autoCategories result = Pattern_to_Categories_cluster (me, thee, k, rc, rs);
+		autoCategories result = PatternList_to_Categories_cluster (me, thee, k, rc, rs);
 		praat_new (result.move(), U"Output");
 	} else {
-		Melder_throw (U"Pattern is empty.");
+		Melder_throw (U"PatternList is empty.");
 	}
 END2 }
 
@@ -597,14 +597,14 @@ END2 }
 /////////////////////////////////////////////////////////////////////////////////////////
 
 DIRECT2 (KNN_patternToDissimilarity) {
-	iam_ONLY (Pattern);
+	iam_ONLY (PatternList);
 	autoFeatureWeights fws = FeatureWeights_create (my nx);
 	autoDissimilarity result = KNN_patternToDissimilarity (me, fws.get());
 	praat_new (result.move(), U"Output");
 END2 }
 
 DIRECT2 (KNN_patternToDissimilarityWithFeatureWeights) {
-	iam_ONLY (Pattern);
+	iam_ONLY (PatternList);
 	thouart_ONLY (FeatureWeights);  
 	if (my nx != thy fweights -> numberOfColumns)
 		Melder_throw (U"The number of features and the number of feature weights should be equal.");
@@ -616,7 +616,7 @@ END2 }
 // Computation of permutation                                                          //
 /////////////////////////////////////////////////////////////////////////////////////////
 
-FORM (KNN_SA_computePermutation, U"To Permutation...", U"Pattern & Categories: To FeatureWeights...") {
+FORM (KNN_SA_computePermutation, U"To Permutation...", U"PatternList & Categories: To FeatureWeights...") {
 	NATURAL (U"Tries per step", U"200")
 	NATURAL (U"Iterations", U"10")
 	POSITIVE (U"Step size", U"10")
@@ -642,21 +642,21 @@ END2 }
 // Computation of feature weights                                                      //
 /////////////////////////////////////////////////////////////////////////////////////////
 
-FORM (FeatureWeights_computeRELIEF, U"Feature weights", U"Pattern & Categories: To FeatureWeights...") {
+FORM (FeatureWeights_computeRELIEF, U"Feature weights", U"PatternList & Categories: To FeatureWeights...") {
 	INTEGER (U"Number of neighbours", U"1")
 	OK2
 DO
-	iam_ONLY (Pattern);
+	iam_ONLY (PatternList);
 	thouart_ONLY (Categories);
 	if (my ny < 2)
-		Melder_throw (U"The Pattern object should contain at least two rows.");
+		Melder_throw (U"The PatternList object should contain at least two rows.");
 	if (my ny != thy size)
-		Melder_throw (U"The number of rows in the Pattern object should equal the number of categories in the Categories object.");
+		Melder_throw (U"The number of rows in the PatternList object should equal the number of categories in the Categories object.");
 	autoFeatureWeights result = FeatureWeights_compute (me, thee, GET_INTEGER (U"Number of neighbours"));
 	praat_new (result.move(), U"Output");
 END2 }
 
-FORM (FeatureWeights_computeWrapperExt, U"Feature weights", U"KNN & Pattern & Categories: To FeatureWeights..") {
+FORM (FeatureWeights_computeWrapperExt, U"Feature weights", U"KNN & PatternList & Categories: To FeatureWeights..") {
 	POSITIVE (U"Learning rate", U"0.02")
 	NATURAL (U"Number of seeds", U"20")
 	POSITIVE (U"Stop at", U"1")
@@ -673,7 +673,7 @@ DO
 	iam_ONLY (KNN);
 	if (my nInstances <= 0)
 		Melder_throw (U"Instance base is empty");
-	thouart_ONLY (Pattern);
+	thouart_ONLY (PatternList);
 	heis_ONLY (Categories);
 	int mode = GET_INTEGER (U"Vote weighting");
 	switch (mode) {
@@ -691,7 +691,7 @@ DO
 	if (k < 1 || k > my nInstances)
 		Melder_throw (U"Please select a value of k such that 0 < k < ", my nInstances + 1, U".");
 	if (thy nx != my input -> nx)
-		Melder_throw (U"The dimensionality of Pattern should be equal to that of the instance base.");
+		Melder_throw (U"The dimensionality of PatternList should be equal to that of the instance base.");
 	autoFeatureWeights result = FeatureWeights_computeWrapperExt (me, thee, him, k, mode, GET_INTEGER (U"Number of seeds"),
 		GET_REAL (U"Learning rate"), GET_REAL (U"Stop at"), (int) GET_INTEGER (U"Optimization"));
 	praat_new (result.move(), U"Output");
@@ -750,13 +750,13 @@ END2 }
 // Creation and processing of auxiliary datatypes                                      //
 /////////////////////////////////////////////////////////////////////////////////////////
 
-FORM (Pattern_create, U"Create Pattern", 0) {
+FORM (Pattern_create, U"Create PatternList", 0) {
 	WORD (U"Name", U"1x1")
 	NATURAL (U"Dimension of a pattern", U"1")
 	NATURAL (U"Number of patterns", U"1")
 	OK2
 DO
-	autoPattern result = Pattern_create (GET_INTEGER (U"Number of patterns"), GET_INTEGER (U"Dimension of a pattern"));
+	autoPatternList result = PatternList_create (GET_INTEGER (U"Number of patterns"), GET_INTEGER (U"Dimension of a pattern"));
 	praat_new (result.move(), GET_STRING (U"Name"));
 END2 }
 
@@ -786,8 +786,8 @@ END2 }
 #ifdef _DEBUG
 
 DIRECT (KNN_debug_KNN_SA_partition)
-    Pattern p = ONLY (classPattern);
-    autoPattern output = Pattern_create (p->ny, p->nx);
+    PatternList p = ONLY (classPatternList);
+    autoPatternList output = PatternList_create (p->ny, p->nx);
     autoNUMvector <long> result (0, p->ny);
     KNN_SA_partition (p, 1, p->ny, result);
 
@@ -828,31 +828,31 @@ DIRECT2 (hint_KNN_and_FeatureWeights_evaluate) {
 END2 }
 
 DIRECT2 (hint_KNN_and_Pattern_classify) {
-	Melder_information (U"You can use the KNN as a classifier by selecting a KNN and a Pattern and choosing \"To Categories...\" or \"To TableOfReal...\".");
+	Melder_information (U"You can use the KNN as a classifier by selecting a KNN and a PatternList and choosing \"To Categories...\" or \"To TableOfReal...\".");
 END2 }
 
 DIRECT2 (hint_KNN_and_Pattern_and_FeatureWeights_classify) {
-	Melder_information (U"You can use the KNN as a classifier by selecting a KNN, a Pattern and an FeatureWeights object and choosing \"To Categories...\" or \"To TableOfReal...\".");
+	Melder_information (U"You can use the KNN as a classifier by selecting a KNN, a PatternList and an FeatureWeights object and choosing \"To Categories...\" or \"To TableOfReal...\".");
 END2 }
 
 DIRECT2 (hint_KNN_and_Pattern_and_Categories_learn) {
-	Melder_information (U"You can train a KNN by selecting a KNN, a Pattern and a Categories object together and choosing \"Learn...\".");
+	Melder_information (U"You can train a KNN by selecting a KNN, a PatternList and a Categories object together and choosing \"Learn...\".");
 END2 }
 
 DIRECT2 (hint_KNN_and_Pattern_and_Categories_evaluate) {
-	Melder_information (U"The accuracy of a KNN can be estimated by selecting a KNN, a test Pattern and the corresponding Categories object and choosing \"Evaluate...\".");
+	Melder_information (U"The accuracy of a KNN can be estimated by selecting a KNN, a test PatternList and the corresponding Categories object and choosing \"Evaluate...\".");
 END2 }
 
 DIRECT2 (hint_KNN_and_Pattern_and_Categories_and_FeatureWeights_evaluate) {
-	Melder_information (U"The accuracy of a KNN can be estimated by selecting a KNN, a test Pattern, an FeatureWeights object, and the corresponding Categories object and choosing \"Evaluate...\".");
+	Melder_information (U"The accuracy of a KNN can be estimated by selecting a KNN, a test PatternList, an FeatureWeights object, and the corresponding Categories object and choosing \"Evaluate...\".");
 END2 }
 
 DIRECT2 (hint_Pattern_and_FeatureWeights_to_Categories) {
-	Melder_information (U"A Pattern object and a FeatureWeights object can be used to compute a fixed number of clusters using the k-means clustering clustering algorithm.");
+	Melder_information (U"A PatternList object and a FeatureWeights object can be used to compute a fixed number of clusters using the k-means clustering clustering algorithm.");
 END2 }
 
 DIRECT2 (hint_Pattern_and_FeatureWeights_to_Dissimilarity) {
-	Melder_information (U"A Dissimilarity matrix can be generated from a Pattern and a FeatureWeights object.");
+	Melder_information (U"A Dissimilarity matrix can be generated from a PatternList and a FeatureWeights object.");
 END2 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
@@ -903,24 +903,24 @@ void praat_contrib_Ola_KNN_init ()
  // praat_addAction1 (classKNN, 0, U"To Permutation...", nullptr, 0, DO_KNN_SA_computePermutation);
  // praat_addAction2 (classKNN, 1, classFeatureWeights, 1, U"To Permutation...", nullptr, 0, DO_KNN_evaluateWithFeatureWeights);
 
-    praat_addAction (classKNN, 1, classPattern, 1, classCategories, 1, U"Learn...", nullptr, 0, DO_KNN_learn);
+    praat_addAction (classKNN, 1, classPatternList, 1, classCategories, 1, U"Learn...", nullptr, 0, DO_KNN_learn);
     praat_addAction2 (classKNN, 1, classFeatureWeights, 1, U"Evaluate...", nullptr, 0, DO_KNN_evaluateWithFeatureWeights);
-    praat_addAction (classKNN, 1, classPattern, 1, classCategories, 1, U"Evaluate...", nullptr, 0, DO_KNN_evaluateWithTestSet);
-    praat_addAction4 (classKNN, 1, classPattern, 1, classCategories, 1, classFeatureWeights, 1, U"Evaluate...", nullptr, 0, DO_KNN_evaluateWithTestSetAndFeatureWeights);
-    praat_addAction (classKNN, 1, classPattern, 1, classCategories, 1, U"To FeatureWeights...", nullptr, 0, DO_FeatureWeights_computeWrapperExt);
-    praat_addAction2 (classKNN, 1, classPattern, 1, U"To Categories...", nullptr, 0, DO_KNN_toCategories);
-    praat_addAction2 (classKNN, 1, classPattern, 1, U"To TableOfReal...", nullptr, 0, DO_KNN_toTableOfReal);
+    praat_addAction (classKNN, 1, classPatternList, 1, classCategories, 1, U"Evaluate...", nullptr, 0, DO_KNN_evaluateWithTestSet);
+    praat_addAction4 (classKNN, 1, classPatternList, 1, classCategories, 1, classFeatureWeights, 1, U"Evaluate...", nullptr, 0, DO_KNN_evaluateWithTestSetAndFeatureWeights);
+    praat_addAction (classKNN, 1, classPatternList, 1, classCategories, 1, U"To FeatureWeights...", nullptr, 0, DO_FeatureWeights_computeWrapperExt);
+    praat_addAction2 (classKNN, 1, classPatternList, 1, U"To Categories...", nullptr, 0, DO_KNN_toCategories);
+    praat_addAction2 (classKNN, 1, classPatternList, 1, U"To TableOfReal...", nullptr, 0, DO_KNN_toTableOfReal);
 
-    praat_addAction (classKNN, 1, classPattern, 1, classFeatureWeights, 1, U"To Categories...", nullptr, 0, DO_KNN_toCategoriesWithFeatureWeights);
-    praat_addAction (classKNN, 1, classPattern, 1, classFeatureWeights, 1, U"To TableOfReal...", nullptr, 0, DO_KNN_toTableOfRealWithFeatureWeights);
+    praat_addAction (classKNN, 1, classPatternList, 1, classFeatureWeights, 1, U"To Categories...", nullptr, 0, DO_KNN_toCategoriesWithFeatureWeights);
+    praat_addAction (classKNN, 1, classPatternList, 1, classFeatureWeights, 1, U"To TableOfReal...", nullptr, 0, DO_KNN_toTableOfRealWithFeatureWeights);
 
-    praat_addAction1 (classPattern, 1, U"To Dissimilarity", nullptr, 1, DO_KNN_patternToDissimilarity);
-    praat_addAction1 (classPattern, 1, U"To Categories...", nullptr, 1, DO_Pattern_to_Categories_cluster);
-    praat_addAction2 (classPattern, 1, classFeatureWeights, 1, U"To Dissimilarity", nullptr, 0, DO_KNN_patternToDissimilarityWithFeatureWeights);
-    praat_addAction2 (classPattern, 1, classFeatureWeights, 1, U"To Categories...", nullptr, 0, DO_Pattern_to_Categories_clusterWithFeatureWeights);
+    praat_addAction1 (classPatternList, 1, U"To Dissimilarity", nullptr, 1, DO_KNN_patternToDissimilarity);
+    praat_addAction1 (classPatternList, 1, U"To Categories...", nullptr, 1, DO_Pattern_to_Categories_cluster);
+    praat_addAction2 (classPatternList, 1, classFeatureWeights, 1, U"To Dissimilarity", nullptr, 0, DO_KNN_patternToDissimilarityWithFeatureWeights);
+    praat_addAction2 (classPatternList, 1, classFeatureWeights, 1, U"To Categories...", nullptr, 0, DO_Pattern_to_Categories_clusterWithFeatureWeights);
 
-    praat_addAction2 (classPattern, 1, classCategories, 1, U"To FeatureWeights...", nullptr, 0, DO_FeatureWeights_computeRELIEF);
-    praat_addAction2 (classPattern, 1, classCategories, 1, U"To KNN Classifier...", nullptr, 0, DO_KNN_Pattern_Categories_to_KNN);
+    praat_addAction2 (classPatternList, 1, classCategories, 1, U"To FeatureWeights...", nullptr, 0, DO_FeatureWeights_computeRELIEF);
+    praat_addAction2 (classPatternList, 1, classCategories, 1, U"To KNN Classifier...", nullptr, 0, DO_KNN_Pattern_Categories_to_KNN);
 
 ///////////
 // DEBUG //
@@ -940,8 +940,8 @@ void praat_contrib_Ola_KNN_init ()
 // Hints //
 ///////////
 
-    praat_addAction1 (classPattern, 0, U"& FeatureWeights: To Categories?", nullptr, 0, DO_hint_Pattern_and_FeatureWeights_to_Categories);
-    praat_addAction1 (classPattern, 0, U"& FeatureWeights: To Dissimilarity?", nullptr, 0, DO_hint_Pattern_and_FeatureWeights_to_Dissimilarity);
+    praat_addAction1 (classPatternList, 0, U"& FeatureWeights: To Categories?", nullptr, 0, DO_hint_Pattern_and_FeatureWeights_to_Categories);
+    praat_addAction1 (classPatternList, 0, U"& FeatureWeights: To Dissimilarity?", nullptr, 0, DO_hint_Pattern_and_FeatureWeights_to_Dissimilarity);
 
     praat_addAction1 (classKNN, 0, U"& FeatureWeights: Evaluate?", nullptr, 0, DO_hint_KNN_and_FeatureWeights_evaluate);
 //  praat_addAction1 (classKNN, 0, U"& FeatureWeights: To Permutation?", nullptr, 0, DO_hint_Pattern_and_FeatureWeights_to_Dissimilarity);
diff --git a/dwsys/NUM2.cpp b/dwsys/NUM2.cpp
index 2223449..697555c 100644
--- a/dwsys/NUM2.cpp
+++ b/dwsys/NUM2.cpp
@@ -1505,42 +1505,42 @@ double NUMfactln (int n) {
 	       (table[n] = NUMlnGamma (n + 1.0));
 }
 
-void NUMnrbis (void (*f) (double x, double *fx, double *dfx, void *closure), double x1, double x2, void *closure, double *root) {
-	double df, dx, dxold, fx, fh, fl, tmp, xh, xl, tol;
+void NUMnrbis (void (*f) (double x, double *fx, double *dfx, void *closure), double xmin, double xmax, void *closure, double *root) {
+	double df, fx, fh, fl, tmp, xh, xl, tol;
 	long itermax = 60;
 
-	(*f) (x1, &fl, &df, closure);
+	(*f) (xmin, &fl, &df, closure);
 	if (fl == 0.0) {
-		*root = x1;
+		*root = xmin;
 		return;
 	}
 
-	(*f) (x2, &fh, &df, closure);
+	(*f) (xmax, &fh, &df, closure);
 	if (fh == 0.0) {
-		*root = x2;
+		*root = xmax;
 		return;
 	}
 
-	if ( (fl > 0.0 && fh > 0.0) || (fl < 0.0 && fh < 0.0)) {
+	if ((fl > 0.0 && fh > 0.0) || (fl < 0.0 && fh < 0.0)) {
 		*root = NUMundefined;
-		Melder_throw (U"Root must be bracketed.");
+		return;
 	}
 
 	if (fl < 0.0) {
-		xl = x1;
-		xh = x2;
+		xl = xmin;
+		xh = xmax;
 	} else {
-		xh = x1;
-		xl = x2;
+		xh = xmin;
+		xl = xmax;
 	}
 
-	dxold = fabs (x2 - x1);
-	dx = dxold;
-	*root = 0.5 * (x1 + x2);
+	double dxold = fabs (xmax - xmin);
+	double dx = dxold;
+	*root = 0.5 * (xmin + xmax);
 	(*f) (*root, &fx, &df, closure);
 
 	for (long iter = 1; iter <= itermax; iter++) {
-		if ( ( ( (*root - xh) * df - fx) * ( (*root - xl) * df - fx) >= 0.0) || (fabs (2.0 * fx) > fabs (dxold * df))) {
+		if ((((*root - xh) * df - fx) * ((*root - xl) * df - fx) >= 0.0) || (fabs (2.0 * fx) > fabs (dxold * df))) {
 			dxold = dx;
 			dx = 0.5 * (xh - xl);
 			*root = xl + dx;
@@ -1576,18 +1576,17 @@ double NUMridders (double (*f) (double x, void *closure), double x1, double x2,
 	/* There is still a problem with this implementation:
 		tol may be zero;
 	*/
-	double x3, x4, d, root = NUMundefined;
-	double f1, f2, f3, f4, tol;
+	double x3, x4, d, root = NUMundefined, tol;
 	long itermax = 100;
 
-	f1 = f (x1, closure);
+	double f1 = f (x1, closure);
 	if (f1 == 0.0) {
 		return x1;
 	}
 	if (f1 == NUMundefined) {
 		return NUMundefined;
 	}
-	f2 = f (x2, closure);
+	double f2 = f (x2, closure);
 	if (f2 == 0.0) {
 		return x2;
 	}
@@ -1595,13 +1594,12 @@ double NUMridders (double (*f) (double x, void *closure), double x1, double x2,
 		return NUMundefined;
 	}
 	if ( (f1 < 0.0 && f2 < 0.0) || (f1 > 0.0 && f2 > 0.0)) {
-		Melder_warning (U"NUMridders: root must be bracketed.");
 		return NUMundefined;
 	}
 
 	for (long iter = 1; iter <= itermax; iter++) {
 		x3 = 0.5 * (x1 + x2);
-		f3 = f (x3, closure);
+		double f3 = f (x3, closure);
 		if (f3 == 0.0) {
 			return x3;
 		}
@@ -1619,7 +1617,7 @@ double NUMridders (double (*f) (double x, void *closure), double x1, double x2,
 
 		if (d == 0.0) {
 			// pb test added because f1 f2 f3 may be 1e-170 or so
-			tol = NUMfpp -> eps * fabs (x3);
+			tol = NUMfpp -> eps * (x3 == 0.0 ? 1.0 : fabs (x3));
 			if (iter > 1 && fabs (x3 - root) < tol) {
 				return root;
 			}
@@ -1648,7 +1646,7 @@ double NUMridders (double (*f) (double x, void *closure), double x1, double x2,
 			d = sqrt (d);
 			if (isnan (d)) {
 				// pb: square root of denormalized small number fails on some computers
-				tol = NUMfpp -> eps * fabs (x3);
+				tol = NUMfpp -> eps * (x3 == 0.0 ? 1.0 : fabs (x3));
 				if (iter > 1 && fabs (x3 - root) < tol) {
 					return root;
 				}
@@ -1676,12 +1674,12 @@ double NUMridders (double (*f) (double x, void *closure), double x1, double x2,
 			} else {
 				d = (x3 - x1) * f3 / d;
 				x4 = f1 - f2 < 0 ? x3 - d : x3 + d;
-				tol = NUMfpp -> eps * fabs (x4);
+				tol = NUMfpp -> eps * (x4 == 0.0 ? 1.0 : fabs (x4));
 				if (iter > 1 && fabs (x4 - root) < tol) {
 					return root;
 				}
 				root = x4;
-				f4 = f (x4, closure);
+				double f4 = f (x4, closure);
 				if (f4 == 0.0) {
 					return root;
 				}
diff --git a/dwsys/NUM2.h b/dwsys/NUM2.h
index bd28a0d..1463c84 100644
--- a/dwsys/NUM2.h
+++ b/dwsys/NUM2.h
@@ -731,18 +731,17 @@ void NUMProcrustes (double **x, double **y, long nPoints,
 	the orthogonal Procrustes transform.
 */
 
-void NUMnrbis (void (*f)(double x, double *fx, double *dfx, void *closure),
-	double x1, double x2, void *closure, double *root);
+void NUMnrbis (void (*f)(double x, double *fx, double *dfx, void *closure), double xmin, double xmax, void *closure, double *root);
 /*
-	Find the root of a function between x1 and x2.
+	Find the root of a function between xmin and xmax.
 	Method: Newton-Raphson with bisection (i.e., derivative is known!).
 	Error condition:
-		root not bracketed.
+		return NUMundefined if root not bracketed.
 */
 
-double NUMridders (double (*f) (double x, void *closure), double x1, double x2, void *closure);
+double NUMridders (double (*f) (double x, void *closure), double xmin, double xmax, void *closure);
 /*
-	Return the root of a function f bracketed in [xlow, xhigh].
+	Return the root of a function f bracketed in [xmin, xmax].
 	Error condition:
 		root not bracketed.
 */
diff --git a/dwtest/English_default.SpeechSynthesizer b/dwtest/English_default.SpeechSynthesizer
new file mode 100644
index 0000000..18d63a9
--- /dev/null
+++ b/dwtest/English_default.SpeechSynthesizer
@@ -0,0 +1,14 @@
+File type = "ooTextFile"
+Object class = "SpeechSynthesizer"
+
+voiceLanguageName = "English" 
+voiceVariantName = "default" 
+wordsPerMinute = 175 
+inputTextFormat = 1 
+inputPhonemeCoding = 1 
+samplingFrequency = 44100 
+wordgap = 0.01 
+pitchAdjustment = 50 
+pitchRange = 50 
+outputPhonemeCoding = 2 
+estimateWordsPerMinute = 1 
diff --git a/dwtest/espeakdata_voices_names_1.47.04.Strings b/dwtest/espeakdata_voices_names_1.47.04.Strings
new file mode 100644
index 0000000..54ac5b2
--- /dev/null
+++ b/dwtest/espeakdata_voices_names_1.47.04.Strings
@@ -0,0 +1,89 @@
+File type = "ooTextFile"
+Object class = "Strings"
+
+numberOfStrings = 84 
+strings []: 
+    strings [1] = "Afrikaans" 
+    strings [2] = "Akan-test" 
+    strings [3] = "Albanian" 
+    strings [4] = "Amharic-test" 
+    strings [5] = "Armenian" 
+    strings [6] = "Armenian-west" 
+    strings [7] = "Azerbaijani-test" 
+    strings [8] = "Bosnian" 
+    strings [9] = "Brazil" 
+    strings [10] = "Bulgarian-test" 
+    strings [11] = "Bulgarian-test" 
+    strings [12] = "Cantonese" 
+    strings [13] = "Catalan" 
+    strings [14] = "Croatian" 
+    strings [15] = "Czech" 
+    strings [16] = "Danish" 
+    strings [17] = "Dari-test" 
+    strings [18] = "Default" 
+    strings [19] = "Divehi-test" 
+    strings [20] = "Dutch-test" 
+    strings [21] = "En-scottish" 
+    strings [22] = "En-westindies" 
+    strings [23] = "English" 
+    strings [24] = "English-us" 
+    strings [25] = "English_rp" 
+    strings [26] = "English_wmids" 
+    strings [27] = "Esperanto" 
+    strings [28] = "Estonian" 
+    strings [29] = "Finnish" 
+    strings [30] = "French" 
+    strings [31] = "French (Belgium)" 
+    strings [32] = "Georgian-test" 
+    strings [33] = "German" 
+    strings [34] = "Greek" 
+    strings [35] = "Greek-ancient" 
+    strings [36] = "Greenlandic" 
+    strings [37] = "Haitian" 
+    strings [38] = "Hindi" 
+    strings [39] = "Hungarian" 
+    strings [40] = "Icelandic-test" 
+    strings [41] = "Indonesian-test" 
+    strings [42] = "Irish-test" 
+    strings [43] = "Italian" 
+    strings [44] = "Kannada" 
+    strings [45] = "Kazakh" 
+    strings [46] = "Kinyarwanda-test" 
+    strings [47] = "Korean" 
+    strings [48] = "Kurdish" 
+    strings [49] = "Lancashire" 
+    strings [50] = "Latin" 
+    strings [51] = "Latvian" 
+    strings [52] = "Lithuanian" 
+    strings [53] = "Lojban" 
+    strings [54] = "Macedonian-test" 
+    strings [55] = "Malayalam" 
+    strings [56] = "Maltese-test" 
+    strings [57] = "Mandarin" 
+    strings [58] = "Nahuatl - classical" 
+    strings [59] = "Nepali-test" 
+    strings [60] = "Northern-sotho" 
+    strings [61] = "Norwegian" 
+    strings [62] = "Papiamento-test" 
+    strings [63] = "Polish" 
+    strings [64] = "Portugal" 
+    strings [65] = "Punjabi-test" 
+    strings [66] = "Romanian" 
+    strings [67] = "Russian_test" 
+    strings [68] = "Serbian" 
+    strings [69] = "Setswana-test" 
+    strings [70] = "Sinhala" 
+    strings [71] = "Slovak" 
+    strings [72] = "Slovenian-test" 
+    strings [73] = "Spanish" 
+    strings [74] = "Spanish-latin-american" 
+    strings [75] = "Swahili-test" 
+    strings [76] = "Swedish" 
+    strings [77] = "Tamil" 
+    strings [78] = "Tatar-test" 
+    strings [79] = "Telugu" 
+    strings [80] = "Turkish" 
+    strings [81] = "Urdu-test" 
+    strings [82] = "Vietnam" 
+    strings [83] = "Welsh-test" 
+    strings [84] = "Wolof-test" 
diff --git a/dwtest/espeakdata_voices_names_1.48.04.Strings b/dwtest/espeakdata_voices_names_1.48.04.Strings
new file mode 100644
index 0000000..15266f4
--- /dev/null
+++ b/dwtest/espeakdata_voices_names_1.48.04.Strings
@@ -0,0 +1,90 @@
+File type = "ooTextFile"
+Object class = "Strings"
+
+numberOfStrings = 85 
+strings []: 
+    strings [1] = "Afrikaans" 
+    strings [2] = "Albanian" 
+    strings [3] = "Amharic-test" 
+    strings [4] = "Aragonese" 
+    strings [5] = "Armenian" 
+    strings [6] = "Armenian-west" 
+    strings [7] = "Assamese-test" 
+    strings [8] = "Azerbaijani-test" 
+    strings [9] = "Basque-test" 
+    strings [10] = "Bengali-test" 
+    strings [11] = "Bosnian" 
+    strings [12] = "Brazil" 
+    strings [13] = "Bulgarian" 
+    strings [14] = "Cantonese" 
+    strings [15] = "Catalan" 
+    strings [16] = "Croatian" 
+    strings [17] = "Czech" 
+    strings [18] = "Danish" 
+    strings [19] = "Default" 
+    strings [20] = "Dutch" 
+    strings [21] = "En-scottish" 
+    strings [22] = "En-westindies" 
+    strings [23] = "English" 
+    strings [24] = "English-north" 
+    strings [25] = "English-us" 
+    strings [26] = "English_rp" 
+    strings [27] = "English_wmids" 
+    strings [28] = "Esperanto" 
+    strings [29] = "Estonian" 
+    strings [30] = "Finnish" 
+    strings [31] = "French" 
+    strings [32] = "French-Belgium" 
+    strings [33] = "Georgian" 
+    strings [34] = "German" 
+    strings [35] = "Greek" 
+    strings [36] = "Greek-ancient" 
+    strings [37] = "Greenlandic" 
+    strings [38] = "Gujarati-test" 
+    strings [39] = "Hindi" 
+    strings [40] = "Hungarian" 
+    strings [41] = "Icelandic" 
+    strings [42] = "Indonesian" 
+    strings [43] = "Irish-gaeilge" 
+    strings [44] = "Italian" 
+    strings [45] = "Kannada" 
+    strings [46] = "Korean-test" 
+    strings [47] = "Kurdish" 
+    strings [48] = "Latin" 
+    strings [49] = "Latvian" 
+    strings [50] = "Lingua_franca_nova" 
+    strings [51] = "Lithuanian" 
+    strings [52] = "Lojban" 
+    strings [53] = "Macedonian" 
+    strings [54] = "Malay" 
+    strings [55] = "Malayalam" 
+    strings [56] = "Mandarin" 
+    strings [57] = "Nahuatl-classical" 
+    strings [58] = "Nepali" 
+    strings [59] = "Norwegian" 
+    strings [60] = "Oriya-test" 
+    strings [61] = "Papiamento-test" 
+    strings [62] = "Persian" 
+    strings [63] = "Persian-pinglish" 
+    strings [64] = "Polish" 
+    strings [65] = "Portugal" 
+    strings [66] = "Punjabi" 
+    strings [67] = "Romanian" 
+    strings [68] = "Russian" 
+    strings [69] = "Scottish-gaelic-test" 
+    strings [70] = "Serbian" 
+    strings [71] = "Sinhala-test" 
+    strings [72] = "Slovak" 
+    strings [73] = "Slovenian-test" 
+    strings [74] = "Spanish" 
+    strings [75] = "Spanish-latin-am" 
+    strings [76] = "Swahili-test" 
+    strings [77] = "Swedish" 
+    strings [78] = "Tamil" 
+    strings [79] = "Telugu-test" 
+    strings [80] = "Turkish" 
+    strings [81] = "Urdu-test" 
+    strings [82] = "Vietnam" 
+    strings [83] = "Vietnam_hue" 
+    strings [84] = "Vietnam_sgn" 
+    strings [85] = "Welsh" 
diff --git a/dwtest/iris_4-2-3-3.FFNet b/dwtest/iris_4-2-3-3.FFNet
new file mode 100644
index 0000000..642f81d
Binary files /dev/null and b/dwtest/iris_4-2-3-3.FFNet differ
diff --git a/dwtest/old_type.Activation b/dwtest/old_type.Activation
new file mode 100644
index 0000000..0875a75
Binary files /dev/null and b/dwtest/old_type.Activation differ
diff --git a/dwtest/old_type.Pattern b/dwtest/old_type.Pattern
new file mode 100644
index 0000000..1710d56
Binary files /dev/null and b/dwtest/old_type.Pattern differ
diff --git a/dwtest/speechsynthesizer_test.praat b/dwtest/speechsynthesizer_test.praat
new file mode 100644
index 0000000..3a0d2c5
--- /dev/null
+++ b/dwtest/speechsynthesizer_test.praat
@@ -0,0 +1,73 @@
+# speechsynthesizer_test.praat
+# djmw 20151209
+
+# show memory leaks of espeak 
+
+Create SpeechSynthesizer: "English", "default"
+numberOfTries = 500
+table = Create Table with column names: "m", numberOfTries, "run bytes"
+for i to numberOfTries
+	selectObject: "SpeechSynthesizer English_default"
+	s = To Sound: "This is some text.", "no"
+	removeObject: s
+	@get_memoryTotalCreated
+	selectObject: table
+	Set numeric value: i, "run", i
+	Set numeric value: i, "bytes", get_memoryTotalCreated.bytes
+endfor
+selectObject: table
+Append column: "diff"
+Formula (column range): "diff", "diff", "self[row,""bytes""]-self[row-1,""bytes""]"
+result = Extract rows where: "row > 1"
+removeObject: table
+selectObject: result
+minimum = Get minimum: "diff"
+Append column: "rdiff"
+Formula (column range): "rdiff", "rdiff",  "self[""diff""] - minimum"
+
+Erase all
+ at asSpectrum: result, 3
+selectObject: asSpectrum.sound
+Select outer viewport: 0, 6, 0, 4
+Draw: 0, 0, 0, 0, "yes", "poles" 
+Text top: "no", "espeak version 1.47.04"
+selectObject: asSpectrum.spectrum
+Select outer viewport: 0, 6, 4, 8
+Draw: 0, 0, 0, 0, "yes"
+Marks bottom every: 1, 50 , "yes", "yes", "yes"
+
+;removeObject: result, asSpectrum.spectrum, asSpectrum.sound
+
+procedure get_memoryTotalCreated
+	.report$ = Report memory use
+	.end = index (.report$, " bytes)")
+	.power = 0
+	.bytes = 0
+	while .end > 0
+		.end -= 1
+		.ch$ = mid$ (.report$, .end, 1)
+		if .ch$ = ","
+			; continue
+		elsif .ch$ = "("
+			.end = 0
+		else
+			.bytes += 10^.power * number (.ch$)
+			.power += 1
+		endif
+	endwhile
+endproc
+
+procedure asSpectrum: .table, .column
+	selectObject: .table
+	.tor = Down to TableOfReal: "run"
+	.mat = To Matrix
+	.matt = Transpose
+	.sound1 = To Sound
+	.sound = Extract one channel: .column
+	Override sampling frequency: 1000
+	.spectrum = To Spectrum: "no"
+	Formula: "if col > 1 then self else 0 fi"
+	removeObject: .tor, .mat, .matt, .sound1
+	selectObject: .spectrum	
+endproc
+
diff --git a/dwtest/test_Activation.praat b/dwtest/test_Activation.praat
deleted file mode 100644
index a271347..0000000
--- a/dwtest/test_Activation.praat
+++ /dev/null
@@ -1,25 +0,0 @@
-# test_Activation.praat
-# djmw 20151020
-
-appendInfoLine: "test_Activation.praat"
-
-for irun to 2
-	nrows = 10
-	for ncols to 10
-		tab = Create simple Matrix: "act", nrows, ncols, "0"
-		act = To Activation
-		Formula:  "randomUniform (0, 0.99)"
-		mat = To Matrix
-		for irow to nrows
-			for icol to ncols
-				val = Get value in cell: irow, icol
-				assert 0<= val and val <= 0.99; 'irow' icol' 'val'
-			endfor
-		endfor
-		removeObject: tab, act, mat
-	endfor
-endfor
-		
-appendInfoLine: "test_Activation.praat OK"
-
-
diff --git a/dwtest/test_ActivationList.praat b/dwtest/test_ActivationList.praat
new file mode 100644
index 0000000..6d16a0c
--- /dev/null
+++ b/dwtest/test_ActivationList.praat
@@ -0,0 +1,34 @@
+# test_ActivationList.praat
+# djmw 20151020, 20160524
+
+appendInfoLine: "test_ActivationList.praat"
+
+appendInfoLine: tab$ , "Read old format from disk"
+old = Read from file: "old_type.Activation"
+removeObject: old
+appendInfoLine: tab$ , "Read old format OK"
+
+ at test_with_old_type
+
+procedure test_with_old_type
+	for irun to 2
+		nrows = 10
+		for ncols to 10
+			tab = Create simple Matrix: "act", nrows, ncols, "0"
+			act = To Activation
+			Formula:  "randomUniform (0, 0.99)"
+			mat = To Matrix
+			for irow to nrows
+				for icol to ncols
+					val = Get value in cell: irow, icol
+					assert 0<= val and val <= 0.99; 'irow' icol' 'val'
+				endfor
+			endfor
+			removeObject: tab, act, mat
+		endfor
+	endfor
+endproc
+		
+appendInfoLine: "test_ActivationList.praat OK"
+
+
diff --git a/dwtest/test_LineSpectralFrequencies.praat b/dwtest/test_LineSpectralFrequencies.praat
new file mode 100644
index 0000000..139568f
--- /dev/null
+++ b/dwtest/test_LineSpectralFrequencies.praat
@@ -0,0 +1,32 @@
+# test_LineSpectralFrequencies.praat
+# djmw 20160511
+
+appendInfoLine: "test_LineSpectralFrequencies"
+samplingFrequency = 8000
+eps = 1e-5
+for i to 9
+	s = Create Sound as tone complex: "toneComplex", 0, 0.5, samplingFrequency, "Cosine", 100, 0, 0, 0
+	appendInfoLine: tab$, "sampling frequency = ", samplingFrequency
+	for nc from 8 to 20
+		appendInfoLine: tab$, tab$, "lpc order = ", nc
+		selectObject: s
+		lpc1 = To LPC (autocorrelation): nc, 0.025, 0.005, 50
+		f1 = To Formant
+		selectObject: lpc1
+		lsf = To LineSpectralFrequencies: 0.0
+		lpc2 = To LPC
+		f2 = To Formant
+		for iformant to nc/2
+			selectObject: f1
+			fi1 = Get value at time: iformant, 0.25, "Hertz", "Linear"
+			selectObject: f2
+			fi2 = Get value at time: iformant, 0.25, "Hertz", "Linear"
+			assert abs (fi1 - fi2) < eps * fi1
+		endfor
+		removeObject: lpc1, f1, lsf, lpc2, f2
+	endfor
+	removeObject: s
+	samplingFrequency += 2000
+endfor
+
+appendInfoLine: "test_LineSpectralFrequencies OK"
diff --git a/dwtest/test_PatternList.praat b/dwtest/test_PatternList.praat
new file mode 100644
index 0000000..bf6d246
--- /dev/null
+++ b/dwtest/test_PatternList.praat
@@ -0,0 +1,34 @@
+# test_PatternList.praat
+# djmw 20151020, 20160524
+
+appendInfoLine: "test_PatternList.praat"
+
+appendInfoLine: tab$ , "Read old format from disk"
+old = Read from file: "old_type.Pattern"
+removeObject: old
+appendInfoLine: tab$ , "Read old format OK"
+
+ at test_with_old_type
+
+procedure test_with_old_type
+	for irun to 2
+		nrows = 10
+		for ncols to 10
+			tab = Create simple Matrix: "act", nrows, ncols, "0"
+			act = To Pattern: 1
+			Formula:  "randomUniform (0, 0.99)"
+			mat = To Matrix
+			for irow to nrows
+				for icol to ncols
+					val = Get value in cell: irow, icol
+					assert 0<= val and val <= 0.99; 'irow' icol' 'val'
+				endfor
+			endfor
+			removeObject: tab, act, mat
+		endfor
+	endfor
+endproc
+		
+appendInfoLine: "test_PatternList.praat OK"
+
+
diff --git a/dwtest/test_Polynomial.praat b/dwtest/test_Polynomial.praat
new file mode 100644
index 0000000..994e104
--- /dev/null
+++ b/dwtest/test_Polynomial.praat
@@ -0,0 +1,67 @@
+# test_Polynomial.praat
+# djmw 20160509
+
+printline test_Polynomial
+
+ at test_roots
+ at test_products
+
+printline test_Polynomial OK
+
+procedure test_roots
+	# random polynomials can behave very wildly. Therefor we are not 
+	# too strictly in checking the differences between generated and 
+	#measured roots
+	.eps1 = 1e-6
+	.eps2 = 1e-6
+	printline ...Roots
+	for .i to 20
+		.numberOfRoots = randomInteger (2, 10)
+		.rootsTable = Create TableOfReal: "r", .numberOfRoots, 1
+		.xmin = randomUniform (-2, 0)
+		.xmax = randomUniform (0, 2)
+		Formula: "randomUniform (.xmin, .xmax)"
+		Sort by column: 1, 0
+		.roots$ = ""
+		for .j to .numberOfRoots
+			.roots[.j]  = Get value: .j, 1
+			.roots$ = .roots$ + string$ (.roots[.j]) + if .j == .numberOfRoots then "" else " " fi
+		endfor
+		.p = Create Polynomial from real zeros:  "p", .xmin, .xmax, .roots$
+		# divide by a root
+		for .j to .numberOfRoots
+			.rootsj = .roots[.j]
+			.remainder = Get remainder: .rootsj
+			.test = abs (.remainder /.rootsj)
+			assert .test < .eps1; '.remainder' '.rootsj' 
+		endfor
+		.roots [0] = .xmin
+		.roots [.numberOfRoots + 1] = .xmax
+		for .j to .numberOfRoots
+			.xmini = randomUniform (.roots[.j-1], .roots[.j])
+			.xmaxi = randomUniform (.roots[.j], .roots[.j+1])
+			.root = Get one real root: .xmini, .xmaxi
+			.rootsj = .roots[.j]
+			.dif = abs (.roots[.j] - .root)
+			assert .dif < .eps2 * abs (.roots[.j]); '.root' '.rootsj' .j
+		endfor
+		removeObject: .p, .rootsTable
+	endfor
+endproc
+
+procedure test_products
+	.eps = 1e-15
+	.p = Create Polynomial from product terms: "p", -3, 3, "1 2 -1 -2"
+	.coefs$ = "1 0 -1 0 0 0 -1 0 1"
+	.strings = Create Strings as tokens: .coefs$
+	.ntokens = Get number of strings
+	for .i to .ntokens
+		selectObject: .strings
+		.coef$ = Get string: .i
+		selectObject: .p
+		.coef = Get coefficient: .i
+		assert abs (number (.coef$) - .coef) < .eps; '.coef'
+	endfor
+	removeObject: .p, .strings
+endproc
+
diff --git a/dwtest/test_SpeechSynthesizer.praat b/dwtest/test_SpeechSynthesizer.praat
index 4de2370..d7ca7c2 100644
--- a/dwtest/test_SpeechSynthesizer.praat
+++ b/dwtest/test_SpeechSynthesizer.praat
@@ -1,23 +1,23 @@
 # test_SpeechSynthesizer.praat
-# djmw 20120130, 20120522
+# djmw 20120130, 20120522, 20160524
 
 appendInfoLine: "SpeechSynthesizer test..."
 
-variantslist = Create copy from FilesInMemory: "variants_names"
-nvariants = Get number of strings
+variantslist = Create copy from FileInMemorySet: "variants_names"
+numberOfVariants = Get number of strings
 
-voiceslist = Create copy from FilesInMemory: "voices_names"
-nvoices = Get number of strings
+voiceslist = Create copy from FileInMemorySet: "voices_names"
+numberOfVoices = Get number of strings
 
 numberOfSounds = 0
-for ivoice to nvoices
+for ivoice to numberOfVoices
 	selectObject: voiceslist
 	voice$ = Get string: ivoice
-	appendInfoLine: tab$, voice$
-	for ivariant to nvariants
+	appendInfo: tab$, voice$, ":"
+	for ivariant to numberOfVariants
 		selectObject: variantslist
 		variant$ = Get string: ivariant
-		appendInfoLine: tab$, tab$, variant$
+		appendInfo:  " ", variant$
 		# some voices have spaces!
 		ss = Create SpeechSynthesizer: voice$, variant$
 		sound = To Sound: "a e u", "no"
@@ -25,8 +25,9 @@ for ivoice to nvoices
 		removeObject: ss, sound
 		numberOfSounds += 1
 	endfor
+	appendInfo: newline$
 endfor
-appendInfoLine: tab$, numberOfSounds, " sounds created/removed"
+appendInfoLine: tab$, numberOfVoices, " voices, ", numberOfSounds, " sounds created/removed"
 appendInfoLine: tab$, "Writing and reading..."
 ss = Create SpeechSynthesizer: voice$, variant$
 Save as text file: "kanweg.SpeechSynthesizer"
diff --git a/dwtools/Activation.cpp b/dwtools/ActivationList.cpp
similarity index 71%
rename from dwtools/Activation.cpp
rename to dwtools/ActivationList.cpp
index f8a0908..3d73c86 100644
--- a/dwtools/Activation.cpp
+++ b/dwtools/ActivationList.cpp
@@ -1,4 +1,4 @@
-/* Activation.cpp
+/* ActivationList.cpp
  *
  * Copyright (C) 1993-2012, 2015 David Weenink
  *
@@ -23,11 +23,11 @@
  djmw 20110304 Thing_new
  */
 
-#include "Activation.h"
+#include "ActivationList.h"
 
-Thing_implement (Activation, Matrix, 2);
+Thing_implement (ActivationList, Matrix, 2);
 
-int _Activation_checkElements (Activation me) {
+int _ActivationList_checkElements (ActivationList me) {
 	for (long i = 1; i <= my ny; i++) {
 		for (long j = 1; j <= my nx; j++) {
 			if (my z[i][j] < 0.0 || my z[i][j] > 1) {
@@ -38,31 +38,31 @@ int _Activation_checkElements (Activation me) {
 	return 1;
 }
 
-void Activation_init (Activation me, long ny, long nx) {
+void ActivationList_init (ActivationList me, long ny, long nx) {
 	Matrix_init (me, 1.0, nx, nx, 1.0, 1.0, 1.0, ny, ny, 1.0, 1.0);
 }
 
-autoActivation Activation_create (long ny, long nx) {
+autoActivationList ActivationList_create (long ny, long nx) {
 	try {
-		autoActivation me = Thing_new (Activation);
-		Activation_init (me.get(), ny, nx);
+		autoActivationList me = Thing_new (ActivationList);
+		ActivationList_init (me.get(), ny, nx);
 		return me;
 	} catch (MelderError) {
 		Melder_throw (U"Activation not created.");
 	}
 }
 
-autoActivation Matrix_to_Activation (Matrix me) {
+autoActivationList Matrix_to_ActivationList (Matrix me) {
 	try {
-		autoActivation thee = Activation_create (my ny, my nx);
+		autoActivationList thee = ActivationList_create (my ny, my nx);
 		NUMmatrix_copyElements (my z, thy z, 1, my ny, 1, my nx);
 		return thee;
 	} catch (MelderError) {
-		Melder_throw (me, U": not converted to Activation.");
+		Melder_throw (me, U": not converted to ActivationList.");
 	}
 }
 
-autoMatrix Activation_to_Matrix (Activation me) {
+autoMatrix ActivationList_to_Matrix (ActivationList me) {
 	try {
 		autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, my ymin, my ymax, my ny, my dy, my y1);
 		NUMmatrix_copyElements (my z, thy z, 1, my ny, 1, my nx);
diff --git a/dwtools/Activation.h b/dwtools/ActivationList.h
similarity index 68%
rename from dwtools/Activation.h
rename to dwtools/ActivationList.h
index 8fcf8b8..5415663 100644
--- a/dwtools/Activation.h
+++ b/dwtools/ActivationList.h
@@ -1,8 +1,8 @@
-#ifndef _Activation_h_
-#define _Activation_h_
+#ifndef _ActivationList_h_
+#define _ActivationList_h_
 /* Activation.h
  * 
- * Copyright (C) 1993-2011, 2015 David Weenink
+ * Copyright (C) 1993-2011, 2015-2016 David Weenink
  * 
  * This program is free oftware; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@
 
 #include "Matrix.h"
 
-Thing_define (Activation, Matrix) {
+Thing_define (ActivationList, Matrix) {
 };
 
 /* Attributes:
@@ -42,15 +42,15 @@ Thing_define (Activation, Matrix) {
    z[iy][ix]		:the activities
 */
 
-void Activation_init (Activation me, long ny, long nx);
+void ActivationList_init (ActivationList me, long ny, long nx);
 
-autoActivation Activation_create (long ny, long nx);
+autoActivationList ActivationList_create (long ny, long nx);
 
-autoActivation Matrix_to_Activation (Matrix me);
+autoActivationList Matrix_to_ActivationList (Matrix me);
 
-autoMatrix Activation_to_Matrix (Activation me);
+autoMatrix ActivationList_to_Matrix (ActivationList me);
 
-int _Activation_checkElements (Activation me);
+int _ActivationList_checkElements (ActivationList me);
 /* Return 1 if all elements are in interval [0,1] else 0. */
 
-#endif /* _Activation_h_ */
+#endif /* _ActivationList_h_ */
diff --git a/dwtools/Discriminant_Pattern_Categories.cpp b/dwtools/Discriminant_PatternList_Categories.cpp
similarity index 70%
rename from dwtools/Discriminant_Pattern_Categories.cpp
rename to dwtools/Discriminant_PatternList_Categories.cpp
index 328b12e..7897a67 100644
--- a/dwtools/Discriminant_Pattern_Categories.cpp
+++ b/dwtools/Discriminant_PatternList_Categories.cpp
@@ -1,4 +1,4 @@
-/* Discriminant_Pattern_Categories.cpp
+/* Discriminant_PatternList_Categories.cpp
  *
  * Copyright (C) 2004-2011, 2015 David Weenink
  *
@@ -20,29 +20,29 @@
  djmw 20040422 Initial version
 */
 
-#include "Discriminant_Pattern_Categories.h"
+#include "Discriminant_PatternList_Categories.h"
 #include "TableOfReal.h"
 #include "Matrix_Categories.h"
 
-autoDiscriminant Pattern_and_Categories_to_Discriminant (Pattern me, Categories thee) {
+autoDiscriminant PatternList_and_Categories_to_Discriminant (PatternList me, Categories thee) {
 	try {
 		autoTableOfReal t = Matrix_and_Categories_to_TableOfReal (me, thee);
 		autoDiscriminant him = TableOfReal_to_Discriminant (t.get());
 		return him;
 	} catch (MelderError) {
-		Melder_throw (U"Discriminant not created from Pattern & Categories.");
+		Melder_throw (U"Discriminant not created from PatternList & Categories.");
 	}
 }
 
-autoCategories Discriminant_and_Pattern_to_Categories (Discriminant me, Pattern thee, int poolCovarianceMatrices, int useAprioriProbabilities) {
+autoCategories Discriminant_and_PatternList_to_Categories (Discriminant me, PatternList thee, int poolCovarianceMatrices, int useAprioriProbabilities) {
 	try {
 		autoTableOfReal t = Matrix_to_TableOfReal (thee);
 		autoClassificationTable ct = Discriminant_and_TableOfReal_to_ClassificationTable (me, t.get(), poolCovarianceMatrices, useAprioriProbabilities);
 		autoCategories him =  ClassificationTable_to_Categories_maximumProbability (ct.get());
 		return him;
 	} catch (MelderError) {
-		Melder_throw (U"Categories not created from Pattern & Discriminant.");
+		Melder_throw (U"Categories not created from PatternList & Discriminant.");
 	}
 }
 
-/* End of file Discriminant_Pattern_Categories.cpp */
+/* End of file Discriminant_PatternList_Categories.cpp */
diff --git a/dwtools/Discriminant_Pattern_Categories.h b/dwtools/Discriminant_PatternList_Categories.h
similarity index 57%
rename from dwtools/Discriminant_Pattern_Categories.h
rename to dwtools/Discriminant_PatternList_Categories.h
index 7103179..241b109 100644
--- a/dwtools/Discriminant_Pattern_Categories.h
+++ b/dwtools/Discriminant_PatternList_Categories.h
@@ -1,6 +1,6 @@
-#ifndef _Discriminant_Pattern_Categories_h_
-#define _Discriminant_Pattern_Categories_h_
-/* Discriminant_Pattern_Categories.h
+#ifndef _Discriminant_PatternList_Categories_h_
+#define _Discriminant_PatternList_Categories_h_
+/* Discriminant_PatternList_Categories.h
  *
  * Copyright (C) 2004-2011, 2015 David Weenink
  *
@@ -23,18 +23,14 @@
  djmw 20110307 Latest modification
 */
 
-#ifndef _Discriminant_h_
-	#include "Discriminant.h"
-#endif
-#ifndef _Pattern_h_
-	#include "Pattern.h"
-#endif
-#ifndef _Categories_h_	
-	#include "Categories.h"
-#endif
 
-autoDiscriminant Pattern_and_Categories_to_Discriminant (Pattern me, Categories thee);
+#include "Discriminant.h"
+#include "PatternList.h"
+#include "Categories.h"
 
-autoCategories Discriminant_and_Pattern_to_Categories (Discriminant me, Pattern thee, int poolCovarianceMatrices, int useAprioriProbabilities);
 
-#endif /* _Discriminant_Pattern_Categories_h_ */
+autoDiscriminant PatternList_and_Categories_to_Discriminant (PatternList me, Categories thee);
+
+autoCategories Discriminant_and_PatternList_to_Categories (Discriminant me, PatternList thee, int poolCovarianceMatrices, int useAprioriProbabilities);
+
+#endif /* _Discriminant_PatternList_Categories_h_ */
diff --git a/dwtools/Eigen_and_Matrix.h b/dwtools/Eigen_and_Matrix.h
index 55b7ed6..40d4e8f 100644
--- a/dwtools/Eigen_and_Matrix.h
+++ b/dwtools/Eigen_and_Matrix.h
@@ -26,7 +26,7 @@
 */
 
 #include "Eigen.h"
-#include "Pattern.h"
+#include "Matrix.h"
 
 autoMatrix Eigen_and_Matrix_to_Matrix_projectRows (Eigen me, Matrix thee, long numberOfDimensionsToKeep);
 /*
diff --git a/dwtools/Excitations.cpp b/dwtools/Excitations.cpp
index 43d1712..65f97ce 100644
--- a/dwtools/Excitations.cpp
+++ b/dwtools/Excitations.cpp
@@ -28,7 +28,7 @@
 
 Thing_implement (ExcitationList, Ordered, 0);
 
-autoPattern ExcitationList_to_Pattern (ExcitationList me, long join) {
+autoPatternList ExcitationList_to_PatternList (ExcitationList me, long join) {
 	try {
 		Melder_assert (my size > 0);
 		Matrix m = my at [1];
@@ -38,7 +38,7 @@ autoPattern ExcitationList_to_Pattern (ExcitationList me, long join) {
 		if ( (my size % join) != 0) {
 			Melder_throw (U"Number of rows is not a multiple of join.");
 		}
-		autoPattern thee = Pattern_create (my size / join, join * m -> nx);
+		autoPatternList thee = PatternList_create (my size / join, join * m -> nx);
 		long r = 0, c = 1;
 		for (long i = 1; i <= my size; i ++) {
 			double *z = my at [i] -> z [1];
@@ -52,7 +52,7 @@ autoPattern ExcitationList_to_Pattern (ExcitationList me, long join) {
 		}
 		return thee;
 	} catch (MelderError) {
-		Melder_throw (me, U": no Pattern created.");
+		Melder_throw (me, U": no PatternList created.");
 	}
 }
 
diff --git a/dwtools/Excitations.h b/dwtools/Excitations.h
index 83e181d..2d002f6 100644
--- a/dwtools/Excitations.h
+++ b/dwtools/Excitations.h
@@ -20,7 +20,7 @@
 
 #include "Collection.h"
 #include "Excitation.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "TableOfReal.h"
 
 
@@ -29,7 +29,7 @@
 Collection_define (ExcitationList, OrderedOf, Excitation) {
 };
 
-autoPattern ExcitationList_to_Pattern (ExcitationList me, long join);
+autoPatternList ExcitationList_to_PatternList (ExcitationList me, long join);
 /* Precondition: my size >= 1, all items have same dimension */
 
 autoTableOfReal ExcitationList_to_TableOfReal (ExcitationList me);
diff --git a/dwtools/Makefile b/dwtools/Makefile
index d6aaf9b..df01bf7 100644
--- a/dwtools/Makefile
+++ b/dwtools/Makefile
@@ -6,7 +6,7 @@ include ../makefile.defs
 
 CPPFLAGS = -I ../num -I ../LPC -I ../fon -I ../sys -I ../stat -I ../dwsys -I ../external/portaudio -I ../external/espeak -I ../EEG -I ../kar
 
-OBJECTS = Activation.o AffineTransform.o \
+OBJECTS = ActivationList.o AffineTransform.o \
 	Categories.o CategoriesEditor.o \
 	Categories_and_Strings.o CCA.o CCA_and_Correlation.o \
 	CC.o CCs_to_DTW.o \
@@ -15,7 +15,7 @@ OBJECTS = Activation.o AffineTransform.o \
 	Configuration_AffineTransform.o \
 	Configuration_and_Procrustes.o  DataModeler.o Distance.o \
 	DTW.o DTW_and_TextGrid.o \
-	Discriminant.o  Discriminant_Pattern_Categories.o \
+	Discriminant.o  Discriminant_PatternList_Categories.o \
 	EditDistanceTable.o EEG_extensions.o \
 	Eigen_and_Matrix.o Eigen_and_Procrustes.o \
 	Eigen_and_TableOfReal.o\
@@ -34,7 +34,7 @@ OBJECTS = Activation.o AffineTransform.o \
 	Matrix_extensions.o \
 	Matrix_Categories.o MDS.o \
 	OptimalCeilingTier.o OptimalCeilingTierEditor.o \
-	Pattern.o PCA.o \
+	PatternList.o PCA.o \
 	Pitch_extensions.o Polynomial.o \
 	Polygon_extensions.o Procrustes.o \
 	Proximity.o \
diff --git a/dwtools/Pattern.cpp b/dwtools/PatternList.cpp
similarity index 65%
rename from dwtools/Pattern.cpp
rename to dwtools/PatternList.cpp
index af85873..ae3f62d 100644
--- a/dwtools/Pattern.cpp
+++ b/dwtools/PatternList.cpp
@@ -1,6 +1,6 @@
-/* Pattern.cpp
+/* PatternList.cpp
  *
- * Copyright (C) 1993-2011, 2015 David Weenink
+ * Copyright (C) 1993-2011, 2015-2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,16 +18,16 @@
 
 /*
  djmw 20020813 GPL header
- djmw 20041203 Added _Pattern_checkElements.
+ djmw 20041203 Added _PatternList_checkElements.
  djmw 20071017 Melder_error<p>
   djmw 20110304 Thing_new
 */
 
-#include "Pattern.h"
+#include "PatternList.h"
 
-Thing_implement (Pattern, Matrix, 2);
+Thing_implement (PatternList, Matrix, 2);
 
-int _Pattern_checkElements (Pattern me) {
+int _PatternList_checkElements (PatternList me) {
 	for (long i = 1; i <= my ny; i++) {
 		for (long j = 1; j <= my nx; j++) {
 			if (my z[i][j] < 0 || my z[i][j] > 1) {
@@ -38,23 +38,23 @@ int _Pattern_checkElements (Pattern me) {
 	return 1;
 }
 
-void Pattern_init (Pattern me, long ny, long nx) {
+void PatternList_init (PatternList me, long ny, long nx) {
 	my ny = ny;
 	my nx = nx;
 	Matrix_init (me, 1, nx, nx, 1, 1, 1, ny, ny, 1, 1);
 }
 
-autoPattern Pattern_create (long ny, long nx) {
+autoPatternList PatternList_create (long ny, long nx) {
 	try {
-		autoPattern me = Thing_new (Pattern);
-		Pattern_init (me.get(), ny, nx);
+		autoPatternList me = Thing_new (PatternList);
+		PatternList_init (me.get(), ny, nx);
 		return me;
 	} catch (MelderError) {
-		Melder_throw (U"Pattern not created.");
+		Melder_throw (U"PatternList not created.");
 	}
 }
 
-void Pattern_normalize (Pattern me, int choice, double pmin, double pmax) {
+void PatternList_normalize (PatternList me, int choice, double pmin, double pmax) {
 	if (pmin == pmax) {
 		(void) Matrix_getWindowExtrema (me, 1, my nx, 1, my ny, & pmin, & pmax);
 	}
@@ -81,7 +81,7 @@ void Pattern_normalize (Pattern me, int choice, double pmin, double pmax) {
 	}
 }
 
-void Pattern_draw (Pattern me, Graphics g, long pattern, double xmin, double xmax, double ymin, double ymax, int garnish) {
+void PatternList_draw (PatternList me, Graphics g, long pattern, double xmin, double xmax, double ymin, double ymax, int garnish) {
 	Matrix_drawRows (me, g, xmin, xmax, pattern - 0.5, pattern + 0.5, ymin, ymax);
 	if (garnish) {
 		Graphics_drawInnerBox (g);
@@ -90,7 +90,7 @@ void Pattern_draw (Pattern me, Graphics g, long pattern, double xmin, double xma
 	}
 }
 
-autoPattern Matrix_to_Pattern (Matrix me, int join) {
+autoPatternList Matrix_to_PatternList (Matrix me, int join) {
 	try {
 		if (join < 1) {
 			join = 1;
@@ -99,7 +99,7 @@ autoPattern Matrix_to_Pattern (Matrix me, int join) {
 			Melder_throw (U"Number of rows is not a multiple of join factor.");
 		}
 
-		autoPattern thee = Pattern_create (my ny / join, join * my nx);
+		autoPatternList thee = PatternList_create (my ny / join, join * my nx);
 
 		long r = 0, c = 1;
 		for (long i = 1; i <= my ny; i++) {
@@ -113,11 +113,11 @@ autoPattern Matrix_to_Pattern (Matrix me, int join) {
 		}
 		return thee;
 	} catch (MelderError) {
-		Melder_throw (me, U": not converted to Pattern.");
+		Melder_throw (me, U": not converted to PatternList.");
 	}
 }
 
-autoMatrix Pattern_to_Matrix (Pattern me) {
+autoMatrix PatternList_to_Matrix (PatternList me) {
 	try {
 		autoMatrix thee = Thing_new (Matrix);
 		my structMatrix :: v_copy (thee.get());
@@ -127,4 +127,14 @@ autoMatrix Pattern_to_Matrix (Pattern me) {
 	}
 }
 
-/* End of file Pattern.cpp */
+autoPatternList ActivationList_to_PatternList (ActivationList me) {
+	try {
+		autoPatternList thee = Thing_new (PatternList);
+		my structMatrix :: v_copy (thee.get());
+		return thee;
+	} catch (MelderError) {
+		Melder_throw (me, U": not converted to PatternList.");
+	}
+}
+
+/* End of file PatternList.cpp */
diff --git a/dwtools/Pattern.h b/dwtools/PatternList.h
similarity index 64%
rename from dwtools/Pattern.h
rename to dwtools/PatternList.h
index 1b226c8..cc8ff1e 100644
--- a/dwtools/Pattern.h
+++ b/dwtools/PatternList.h
@@ -1,5 +1,5 @@
-#ifndef _Pattern_h_
-#define _Pattern_h_
+#ifndef _PatternList_h_
+#define _PatternList_h_
 /* Pattern.h
  *
  * Copyright (C) 1993-2011 David Weenink
@@ -19,8 +19,9 @@
  */
 
 #include "Matrix.h"
+#include "ActivationList.h"
 
-Thing_define (Pattern, Matrix) {
+Thing_define (PatternList, Matrix) {
 };
 
 /* Attributes:
@@ -36,23 +37,25 @@ Thing_define (Pattern, Matrix) {
    z[iy][ix]		:the inputs. All elements in interval [0,1].
 */
 
-void Pattern_init (Pattern me, long ny, long nx);
+void PatternList_init (PatternList me, long ny, long nx);
 
-autoPattern Pattern_create (long ny, long nx);
+autoPatternList PatternList_create (long ny, long nx);
 
-void Pattern_normalize (Pattern me, int choice, double pmin, double pmax);
+void PatternList_normalize (PatternList me, int choice, double pmin, double pmax);
 /* choice == 1: z[i][j] = (z[i][j]-pmin) / (pmax-pmin);
  * choice == 2: z[i][j] *= 1.0 / sum(j=1,j=nx, z[i][j]-pmin)
  */
 
-void Pattern_draw (Pattern me, Graphics g, long pattern, double xmin, double xmax,
+void PatternList_draw (PatternList me, Graphics g, long pattern, double xmin, double xmax,
 	double ymin, double ymax, int garnish);
 
-autoPattern Matrix_to_Pattern (Matrix me, int join);
+autoPatternList Matrix_to_PatternList (Matrix me, int join);
 
-autoMatrix Pattern_to_Matrix (Pattern me);
+autoMatrix PatternList_to_Matrix (PatternList me);
 
-int _Pattern_checkElements (Pattern me);
+autoPatternList ActivationList_to_PatternList (ActivationList me);
+
+int _PatternList_checkElements (PatternList me);
 /* Return 1 if all elements are in interval [0,1] else 0. */
 
-#endif /* _Pattern_h_ */
+#endif /* _PatternList_h_ */
diff --git a/dwtools/Polynomial.cpp b/dwtools/Polynomial.cpp
index a69945c..703ae5c 100644
--- a/dwtools/Polynomial.cpp
+++ b/dwtools/Polynomial.cpp
@@ -59,18 +59,18 @@
 /* Evaluate polynomial and derivative jointly
 	c[1..n] -> degree n-1 !!
 */
-static void Polynomial_evaluate2 (Polynomial me, double x, double *f, double *df) {
-	long double p = my coefficients[my numberOfCoefficients], dp = 0, xc = x;
+void Polynomial_evaluateWithDerivative (Polynomial me, double x, double *f, double *df) {
+	long double p = my coefficients [my numberOfCoefficients], dp = 0.0, xc = x;
 
 	for (long i = my numberOfCoefficients - 1; i > 0; i--) {
 		dp = dp * xc + p;
-		p =  p * xc + my coefficients[i];
+		p =  p * xc + my coefficients [i];
 	}
 	*f = (double) p; *df = (double) dp;
 }
 
 /* Get value and derivative */
-static void Polynomial_evaluate2_z (Polynomial me, dcomplex *z, dcomplex *p, dcomplex *dp) {
+static void Polynomial_evaluateWithDerivative_z (Polynomial me, dcomplex *z, dcomplex *p, dcomplex *dp) {
 	long double pr = my coefficients[my numberOfCoefficients], pi = 0;
 	long double dpr = 0, dpi = 0, x = z -> re, y = z -> im;
 
@@ -86,6 +86,30 @@ static void Polynomial_evaluate2_z (Polynomial me, dcomplex *z, dcomplex *p, dco
 	dp -> re =  (double) dpr; dp -> im = (double) dpi;
 }
 
+
+void Polynomial_evaluateDerivatives (Polynomial me, double x, double *derivatives, long numberOfDerivatives) {
+	/* Evaluate polynomial c[1]+c[2]*x+...degree*x^degree in derivative[0] and derivatives [1..numberOfDerivatives] */
+	long degree = my numberOfCoefficients - 1;
+	numberOfDerivatives = numberOfDerivatives > degree ? degree : numberOfDerivatives;
+	
+	derivatives [0] = my coefficients [my numberOfCoefficients];
+	for (long j = 1; j <= numberOfDerivatives; j ++) {
+		derivatives [j] = 0.0;
+	}
+	for (long i = degree - 1; i >= 0; i--) {
+		long n = (numberOfDerivatives < (degree - i) ? numberOfDerivatives : degree - i);
+		for (long j = n; j >= 1; j--) {
+			derivatives [j] = derivatives [j] * x +  derivatives [j - 1];
+		}
+		derivatives [0] = derivatives [0] * x + my coefficients [i + 1];  // Evaluate polynomial (Horner)
+	}
+	double fact = 1.0;
+	for (long j = 2; j <= numberOfDerivatives; j ++) {
+		fact *= j;
+		derivatives [j] *= fact;
+	}
+}
+
 /*
 	void polynomial_divide (double *u, long m, double *v, long n, double *q, double *r);
 
@@ -120,6 +144,7 @@ static void polynomial_divide (double *u, long m, double *v, long n, double *q,
 	}
 }
 
+
 static void Polynomial_polish_realroot (Polynomial me, double *x, long maxit) {
 	double xbest = *x, pmin = 1e308;
 	if (! NUMfpp) {
@@ -128,7 +153,7 @@ static void Polynomial_polish_realroot (Polynomial me, double *x, long maxit) {
 
 	for (long i = 1; i <= maxit; i++) {
 		double p, dp;
-		Polynomial_evaluate2 (me, *x, &p, &dp);
+		Polynomial_evaluateWithDerivative (me, *x, &p, &dp);
 		double fabsp = fabs (p);
 		if (fabsp > pmin || fabs (fabsp - pmin) < NUMfpp -> eps) {
 			// We stop because the approximation gets worse or we cannot get closer anymore
@@ -155,7 +180,7 @@ static void Polynomial_polish_complexroot_nr (Polynomial me, dcomplex *z, long m
 
 	for (long i = 1; i <= maxit; i++) {
 		dcomplex p, dp;
-		Polynomial_evaluate2_z (me, z, &p, &dp);
+		Polynomial_evaluateWithDerivative_z (me, z, &p, &dp);
 		double fabsp = dcomplex_abs (p);
 		if (fabsp > pmin || fabs (fabsp - pmin) < NUMfpp -> eps) {
 			// We stop because the approximation gets worse.
@@ -290,9 +315,18 @@ void structFunctionTerms :: v_getExtrema (double x1, double x2, double *p_xmin,
 	}
 }
 
+static inline void FunctionTerms_extendCapacityIf (FunctionTerms me, long minimum) {
+	if (my _capacity < minimum) {
+		NUMvector_append<double> (& my coefficients, 1, & minimum);
+		my _capacity = minimum;
+	}
+}
+
+
 void FunctionTerms_init (FunctionTerms me, double xmin, double xmax, long numberOfCoefficients) {
 	my coefficients = NUMvector<double> (1, numberOfCoefficients);
 	my numberOfCoefficients = numberOfCoefficients;
+	my _capacity = numberOfCoefficients;
 	my xmin = xmin; my xmax = xmax;
 }
 
@@ -505,7 +539,7 @@ void FunctionTerms_setCoefficient (FunctionTerms me, long index, double value) {
 
 /********** Polynomial ***********************************************/
 
-Thing_implement (Polynomial, FunctionTerms, 0);
+Thing_implement (Polynomial, FunctionTerms, 1);
 
 double structPolynomial :: v_evaluate (double x) {
 	long double p = coefficients [numberOfCoefficients];
@@ -583,10 +617,10 @@ void structPolynomial :: v_getExtrema (double x1, double x2, double *p_xmin, dou
 	}
 }
 
-autoPolynomial Polynomial_create (double lxmin, double lxmax, long degree) {
+autoPolynomial Polynomial_create (double xmin, double xmax, long degree) {
 	try {
 		autoPolynomial me = Thing_new (Polynomial);
-		FunctionTerms_init (me.get(), lxmin, lxmax, degree + 1);
+		FunctionTerms_init (me.get(), xmin, xmax, degree + 1);
 		return me;
 	} catch (MelderError) {
 		Melder_throw (U"Polynomial not created.");
@@ -710,27 +744,134 @@ autoPolynomial Polynomial_getDerivative (Polynomial me) {
 	}
 }
 
-autoPolynomial Polynomial_getPrimitive (Polynomial me) {
+autoPolynomial Polynomial_getPrimitive (Polynomial me, double constant) {
 	try {
 		autoPolynomial thee = Polynomial_create (my xmin, my xmax, my numberOfCoefficients);
 		for (long i = 1; i <= my numberOfCoefficients; i++) {
 			thy coefficients[i + 1] = my coefficients[i] / i;
 		}
+		thy coefficients [1] = constant;
 		return thee;
 	} catch (MelderError) {
 		Melder_throw (me, U": no primitive created.");
 	}
 }
 
+/* P(x)= (x-roots[1])*(x-roots[2])*..*(x-roots[numberOfRoots]) */
+void Polynomial_initFromRealRoots (Polynomial me, double *roots, long numberOfRoots) {
+	try {
+		if (numberOfRoots < 1) {
+			return;
+		}
+		FunctionTerms_extendCapacityIf (me, numberOfRoots + 1);
+		double *c = & my coefficients [1];
+		long n = 1;
+		c [0] = - roots[1];
+		c [1] = 1.0;
+		for (long i = 2; i <= numberOfRoots; i++) {
+			c [n + 1] = c [n];
+			for (long j = n; j >= 1; j --) {
+				c [j] = c [j - 1] - c [j] * roots [i];
+			}
+			c [0] *= -roots [i];
+			n ++;
+		}
+		my numberOfCoefficients = n + 1;
+	} catch (MelderError) {
+		Melder_throw (me, U": not initalized from real roots.");
+	}
+}
+
+autoPolynomial Polynomial_createFromRealRootsString (double xmin, double xmax, const char32 *s) {
+	try {
+		autoPolynomial me = Thing_new (Polynomial);
+		long numberOfRoots;
+		autoNUMvector<double> roots (NUMstring_to_numbers (s, & numberOfRoots), 1);
+		FunctionTerms_init (me.get(), xmin, xmax, numberOfRoots + 1);
+		Polynomial_initFromRealRoots (me.get(), roots.peek(), numberOfRoots);
+		return me;
+	} catch (MelderError) {
+		Melder_throw (U"Polynomial not created from roots.");
+	}
+	
+}
+
+/* Product (i=1; numberOfSecondOrderTerms; (1 + a*x + x^2)
+ * Postcondition : my numberOfCoeffcients = 2*numberOfTerms1+1
+ */
+void Polynomial_initFromProductOfSecondOrderTerms (Polynomial me, double *a, long numberOfSecondOrderTerms) {
+	if (numberOfSecondOrderTerms < 1) {
+		return;
+	}
+	FunctionTerms_extendCapacityIf (me, 2 * numberOfSecondOrderTerms + 1);
+	my coefficients [1] = my coefficients [3] = 1.0;
+	my coefficients [2] = a [1];
+	long numberOfCoefficients = 3;
+	for (long i = 2; i <= numberOfSecondOrderTerms; i++) {
+		my coefficients [numberOfCoefficients + 1] = a [i] * my coefficients [numberOfCoefficients] + my coefficients [numberOfCoefficients - 1];
+		my coefficients [numberOfCoefficients + 2] = my coefficients [numberOfCoefficients];
+		for (long j = numberOfCoefficients; j > 2; j --) {
+			my coefficients [j] += a [i] * my coefficients [j - 1] + my coefficients [j - 2];
+		}
+		my coefficients [2] += a [i]; // a [i] * my coefficients [1]
+		numberOfCoefficients += 2;
+	}
+	my numberOfCoefficients = numberOfCoefficients;
+}
+
+autoPolynomial Polynomial_createFromProductOfSecondOrderTermsString (double xmin, double xmax, const char32 *s) {
+	try {
+		autoPolynomial me = Thing_new (Polynomial);
+		long numberOfTerms;
+		autoNUMvector<double> a (NUMstring_to_numbers (s, & numberOfTerms), 1);
+		FunctionTerms_init (me.get(), xmin, xmax, 2 * numberOfTerms + 1);
+		Polynomial_initFromProductOfSecondOrderTerms (me.get(), a.peek(), numberOfTerms);
+		return me;
+	} catch (MelderError) {
+		Melder_throw (U"Polynomial not created from second order terms string.");
+	}
+}
+
 double Polynomial_getArea (Polynomial me, double xmin, double xmax) {
 	if (xmax >= xmin) {
 		xmin = my xmin; xmax = my xmax;
 	}
-	autoPolynomial p = Polynomial_getPrimitive (me);
+	autoPolynomial p = Polynomial_getPrimitive (me, 0);
 	double area = FunctionTerms_evaluate (p.get(), xmax) - FunctionTerms_evaluate (p.get(), xmin);
 	return area;
 }
 
+/* P(x) * (x-a)
+ * Postcondition: my numberOfCoefficients = old_numberOfCoefficients + 1 
+ */
+void Polynomial_multiply_firstOrderFactor (Polynomial me, double factor) { 
+	long n = my numberOfCoefficients;
+	FunctionTerms_extendCapacityIf (me, n + 1);
+	
+	my coefficients [n + 1] = my coefficients [n];
+	for (long j = n; j >= 2; j --) {
+		my coefficients [j] = my coefficients [j - 1] - my coefficients [j] * factor;
+	}
+	my coefficients [1] *= -factor;
+	my numberOfCoefficients += 1;
+}
+
+/* P(x) * (x^2 - a)
+ * Postcondition: my numberOfCoefficients = old_numberOfCoefficients + 2
+ */
+void Polynomial_multiply_secondOrderFactor (Polynomial me, double factor) {
+	long n = my numberOfCoefficients;
+	FunctionTerms_extendCapacityIf (me, n + 2);
+	my coefficients [n + 2] = my coefficients [n];
+	my coefficients [n + 1] = my coefficients [n - 1];
+	for (long j = n; j >= 3; j --) {
+		my coefficients [j] = my coefficients [j - 2] - factor * my coefficients [j];
+	}
+	my coefficients [2] *= - factor;
+	my coefficients [1] *= - factor;
+	my numberOfCoefficients += 2;	
+}
+
 autoPolynomial Polynomials_multiply (Polynomial me, Polynomial thee) {
 	try {
 		long n1 = my numberOfCoefficients, n2 = thy numberOfCoefficients;
@@ -1107,7 +1248,7 @@ void Roots_and_Polynomial_polish (Roots me, Polynomial thee) {
 	long i = my min, maxit = 80;
 	while (i <= my max) {
 		double im = my v[i].im, re = my v[i].re;
-		if (im != 0) {
+		if (im != 0.0) {
 			Polynomial_polish_complexroot_nr (thee, & my v[i], maxit);
 			if (i < my max && im == -my v[i + 1].im && re == my v[i + 1].re) {
 				my v[i + 1].re = my v[i].re; my v[i + 1].im = -my v[i].im;
@@ -1120,15 +1261,76 @@ void Roots_and_Polynomial_polish (Roots me, Polynomial thee) {
 	}
 }
 
-autoPolynomial Roots_to_Polynomial (Roots me) {
+autoPolynomial Roots_to_Polynomial (Roots me, bool rootsAreReal) {
 	try {
 		(void) me;
-		throw MelderError();
+		autoPolynomial thee;
+		if (! rootsAreReal) {
+			throw MelderError();
+		}
+		return thee;
 	} catch (MelderError) {
 		Melder_throw (U"Not implemented yet");
 	}
 }
 
+static void dpoly_nr (double x, double *f, double *df, void *closure) {
+	Polynomial_evaluateWithDerivative ((Polynomial) closure, x, f, df);
+}
+
+double Polynomial_findOneSimpleRealRoot_nr (Polynomial me, double xmin, double xmax) {	
+	double root;
+	NUMnrbis (dpoly_nr, xmin, xmax, me, &root);
+	return root;
+}
+
+static double dpoly_r (double x, void *closure) {
+	return Polynomial_evaluate ((Polynomial) closure, x);
+}
+
+double Polynomial_findOneSimpleRealRoot_ridders (Polynomial me, double xmin, double xmax) {	
+	return NUMridders (dpoly_r, xmin, xmax, me);
+}
+
+void Polynomial_divide_firstOrderFactor (Polynomial me, double factor, double *p_remainder) { // P(x)/(x-a)
+	double remainder = NUMundefined;
+	if (my numberOfCoefficients > 1) {
+		remainder = my coefficients [my numberOfCoefficients];
+		for (long j = my numberOfCoefficients - 1; j > 0; j --) {
+			double tmp = my coefficients [j];
+			my coefficients [j] = remainder;
+			remainder = tmp + remainder * factor;
+		}
+		my numberOfCoefficients --;
+	} else {
+		my coefficients [1] = 0.0;
+	}
+	if (p_remainder) {
+		*p_remainder = remainder;
+	}
+}
+
+void Polynomial_divide_secondOrderFactor (Polynomial me, double factor) {
+	if (my numberOfCoefficients > 2) {
+		long n = my numberOfCoefficients;
+		/* c[1]+c[2]*x...c[n+1]*x^n / (x^2 - a) = r[1]+r[2]*x+...r[n-1]x^(n-2) + possible remainder a[1]+a[2]*x)
+		 * r[j] = c[j+2]+factor*r[j+2] */
+		double cjp2 = my coefficients [n];
+		double cjp1 = my coefficients [n - 1];
+		my coefficients [n] = my coefficients [n - 1] = 0.0;
+		for (long j = n - 2; j > 0; j --) {
+			double cj = my coefficients [j];
+			my coefficients [j] = cjp2 + factor * my coefficients [j + 2];
+			cjp2 = cjp1;
+			cjp1 = cj;
+		}
+		my numberOfCoefficients -= 2;
+	} else {
+		my numberOfCoefficients = 1;
+		my coefficients [1] = 0.0;
+	}
+}
+
 void Roots_setRoot (Roots me, long index, double re, double im) {
 	if (index < my min || index > my max) {
 		Melder_throw (U"Index must be in interval [1, ", my max, U"].");
diff --git a/dwtools/Polynomial.h b/dwtools/Polynomial.h
index 0757a4e..908b621 100644
--- a/dwtools/Polynomial.h
+++ b/dwtools/Polynomial.h
@@ -107,22 +107,60 @@ void Polynomial_evaluate_z (Polynomial me, dcomplex *z, dcomplex *p);
 /* Evaluate at complex z = x + iy */
 
 
+/* Product (i=1; numberOfTerms; (1 + a*x + x^2)
+ * Precondition : my numberOfCoeffcients >= 3+2*numberOfOmegas
+ * 	Polynomial is uses as a "buffer". We define it one and reuse it 
+ */
+void Polynomial_initFromProductOfSecondOrderTerms (Polynomial me, double *a, long numberOfTerms);
+autoPolynomial Polynomial_createFromProductOfSecondOrderTermsString (double xmin, double xmax, const char32 *s);
+
+void Polynomial_initFromRealRoots (Polynomial me, double *roots, long numberOfRoots);
+autoPolynomial Polynomial_createFromRealRootsString (double xmin, double xmax, const char32 *s);
+
 double Polynomial_getArea (Polynomial me, double xmin, double xmax);
 
 autoPolynomial Polynomial_getDerivative (Polynomial me);
 
-autoPolynomial Polynomial_getPrimitive (Polynomial me);
+autoPolynomial Polynomial_getPrimitive (Polynomial me, double constant);
 
 void Polynomial_draw (Polynomial me, Graphics g, double xmin, double xmax, double ymin, double ymax, int garnish);
 
 double Polynomial_evaluate (Polynomial me, double x);
 
+void Polynomial_evaluateWithDerivative (Polynomial me, double x, double *fx, double *dfx);
+
+void Polynomial_evaluateDerivatives (Polynomial me, double x, double *derivatives /*[0.. numberOfDerivatives]*/, long numberOfDerivatives);
+/* derivatives[0] = Polynomial_evaluate (me, x); */
+
 void Polynomial_evaluateTerms (Polynomial me, double x, double terms[]);
 
 autoPolynomial Polynomials_multiply (Polynomial me, Polynomial thee);
 
+void Polynomial_multiply_firstOrderFactor (Polynomial me, double factor);
+/* P(x) * (x-factor)
+ * Postcondition: my numberOfCoefficients = old_numberOfCoefficients + 1 
+ */
+
+void Polynomial_multiply_secondOrderFactor (Polynomial me, double factor);
+/* P(x) * (x^2 - a)
+ * Postcondition: my numberOfCoefficients = old_numberOfCoefficients + 2
+ */
+
 void Polynomials_divide (Polynomial me, Polynomial thee, autoPolynomial *q, autoPolynomial *r);
 
+void Polynomial_divide_firstOrderFactor (Polynomial me, double factor, double *p_remainder); // P(x)/(x-a)
+/* Functions: calculate coefficients of new polynomial P(x)/(x-a)
+ * if p_remainder != nullptr it will contain 
+ *		remainder after dividing by monomial factor x-a.
+ * 		NUMundefined if my numberOfCoefficients == 1 (error condition)
+ * Postcondition: my numberOfCoefficients reduced by 1 
+*/
+
+void Polynomial_divide_secondOrderFactor (Polynomial me, double factor);
+/* P(x) / (x^2 - a)
+ * Postcondition: my numberOfCoefficients = old_numberOfCoefficients - 2
+ */
+
 Thing_define (LegendreSeries, FunctionTerms) {
 	// overridden methods:
 	public:
@@ -167,9 +205,15 @@ autoSpectrum Roots_to_Spectrum (Roots me, double nyquistFrequency, long numberOf
 autoRoots Polynomial_to_Roots (Polynomial me);
 /* Find roots of polynomial and polish them */
 
+double Polynomial_findOneSimpleRealRoot_nr (Polynomial me, double xmin, double xmax);
+double Polynomial_findOneSimpleRealRoot_ridders (Polynomial me, double xmin, double xmax);
+/* Preconditions: there must be exactly one root in the [xmin, xmax] interval;
+ * Root will be found by newton-raphson with bisecting
+ */
+
 void Roots_and_Polynomial_polish (Roots me, Polynomial thee);
 
-autoPolynomial Roots_to_Polynomial (Roots me);
+autoPolynomial Roots_to_Polynomial (Roots me, bool rootsAreReal);
 
 autoPolynomial TableOfReal_to_Polynomial (TableOfReal me, long degree, long xcol, long ycol, long scol);
 
diff --git a/dwtools/Polynomial_def.h b/dwtools/Polynomial_def.h
index b68dbc3..775a709 100644
--- a/dwtools/Polynomial_def.h
+++ b/dwtools/Polynomial_def.h
@@ -1,6 +1,6 @@
 /* Polynomial_def.h
  *
- * Copyright (C) 1993-2002 David Weenink
+ * Copyright (C) 1993-2002, 2016 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,14 @@ oo_DEFINE_CLASS (FunctionTerms, Function)
 
 	oo_LONG (numberOfCoefficients)
 	oo_DOUBLE_VECTOR (coefficients, numberOfCoefficients)
+	
+	#if !oo_READING && !oo_WRITING
+		oo_LONG (_capacity)
+	#endif
+		
+	#if oo_READING
+		_capacity = numberOfCoefficients;
+	#endif
 
 	#if oo_DECLARING
 		// new methods:
diff --git a/dwtools/Spectrogram_extensions.cpp b/dwtools/Spectrogram_extensions.cpp
index 7f11874..4cf62e3 100644
--- a/dwtools/Spectrogram_extensions.cpp
+++ b/dwtools/Spectrogram_extensions.cpp
@@ -376,7 +376,8 @@ void BandFilterSpectrogram_drawSpectrumAtNearestTimeSlice (BandFilterSpectrogram
 void BarkSpectrogram_drawSekeyHansonFilterFunctions (BarkSpectrogram me, Graphics g, bool xIsHertz, int fromFilter, int toFilter, double zmin, double zmax, bool yscale_dB, double ymin, double ymax, int garnish) {
 	double xmin = zmin, xmax = zmax;
 	if (zmin >= zmax) {
-		zmin = my ymin; zmax = my ymax;
+		zmin = my ymin;
+		zmax = my ymax;
 		xmin = xIsHertz ? my v_frequencyToHertz (zmin) : zmin;
 		xmax = xIsHertz ? my v_frequencyToHertz (zmax) : zmax;
 	}
@@ -384,13 +385,14 @@ void BarkSpectrogram_drawSekeyHansonFilterFunctions (BarkSpectrogram me, Graphic
 		zmin = my v_hertzToFrequency (xmin); zmax = my v_hertzToFrequency (xmax);
 	}
 	if (ymin >= ymax) {
-		ymin = yscale_dB ? -60 : 0.0;
+		ymin = yscale_dB ? -60.0 : 0.0;
 		ymax = yscale_dB ? 0.0 : 1.0;
 	}
 	fromFilter = fromFilter <= 0 ? 1 : fromFilter;
-	toFilter = toFilter <= 0 || toFilter > my ny ? my ny : toFilter;
+	toFilter = ( toFilter <= 0 || toFilter > my ny ? my ny : toFilter );
 	if (fromFilter > toFilter) {
-		fromFilter = 1; toFilter = my ny;
+		fromFilter = 1;
+		toFilter = my ny;
 	}
 	long n = xIsHertz ? 1000 : 500;
 	autoNUMvector<double> xz (1, n), xhz (1,n), y (1, n);
@@ -579,7 +581,7 @@ autoIntensity BandFilterSpectrogram_to_Intensity (BandFilterSpectrogram me) {
 	try {
 		autoIntensity thee = Intensity_create (my xmin, my xmax, my nx, my dx, my x1);
 		for (long j = 1; j <= my nx; j++) {
-			double p = 0;
+			double p = 0.0;
 			for (long i = 1; i <= my ny; i++) {
 				p += my z[i][j]; // we add power
 			}
@@ -593,7 +595,7 @@ autoIntensity BandFilterSpectrogram_to_Intensity (BandFilterSpectrogram me) {
 
 void BandFilterSpectrogram_equalizeIntensities (BandFilterSpectrogram me, double intensity_db) {
 	for (long j = 1; j <= my nx; j++) {
-		double p = 0;
+		double p = 0.0;
 		for (long i = 1; i <= my ny; i++) {
 			p += my z[i][j];
 		}
diff --git a/dwtools/TableOfReal_extensions.cpp b/dwtools/TableOfReal_extensions.cpp
index f751709..e0d85db 100644
--- a/dwtools/TableOfReal_extensions.cpp
+++ b/dwtools/TableOfReal_extensions.cpp
@@ -250,8 +250,8 @@ autoTableOfReal TableOfReal_transpose (TableOfReal me) {
 	}
 }
 
-void TableOfReal_to_Pattern_and_Categories (TableOfReal me, long fromrow, long torow, long fromcol, long tocol,
-	autoPattern *p, autoCategories *c)
+void TableOfReal_to_PatternList_and_Categories (TableOfReal me, long fromrow, long torow, long fromcol, long tocol,
+	autoPatternList *p, autoCategories *c)
 {
 	try {
 		long ncol = my numberOfColumns, nrow = my numberOfRows;
@@ -273,7 +273,7 @@ void TableOfReal_to_Pattern_and_Categories (TableOfReal me, long fromrow, long t
 
 		nrow = torow - fromrow + 1;
 		ncol = tocol - fromcol + 1;
-		autoPattern ap = Pattern_create (nrow, ncol);
+		autoPatternList ap = PatternList_create (nrow, ncol);
 		autoCategories ac = Categories_create ();
 
 		long row = 1;
@@ -289,7 +289,7 @@ void TableOfReal_to_Pattern_and_Categories (TableOfReal me, long fromrow, long t
 		*p = ap.move();
 		*c = ac.move();
 	} catch (MelderError) {
-		Melder_throw (U"Pattern and Categories not created from TableOfReal.");
+		Melder_throw (U"PatternList and Categories not created from TableOfReal.");
 	}
 }
 
diff --git a/dwtools/TableOfReal_extensions.h b/dwtools/TableOfReal_extensions.h
index ef24335..9edfc76 100644
--- a/dwtools/TableOfReal_extensions.h
+++ b/dwtools/TableOfReal_extensions.h
@@ -20,13 +20,13 @@
 
 #include "TableOfReal.h"
 #include "Collection.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "Categories.h"
 #include "Strings_.h"
 #include "SSCP.h"
 
-void TableOfReal_to_Pattern_and_Categories(TableOfReal me, long fromrow, long torow, long fromcol, long tocol,
-	autoPattern *p, autoCategories *c);
+void TableOfReal_to_PatternList_and_Categories(TableOfReal me, long fromrow, long torow, long fromcol, long tocol,
+	autoPatternList *p, autoCategories *c);
 
 autoTableOfReal TableOfReal_transpose (TableOfReal me);
 
diff --git a/dwtools/Table_extensions.h b/dwtools/Table_extensions.h
index 4d483ff..0385acd 100644
--- a/dwtools/Table_extensions.h
+++ b/dwtools/Table_extensions.h
@@ -25,7 +25,6 @@
 
 #include "TableOfReal.h"
 #include "Collection.h"
-#include "Pattern.h"
 #include "Categories.h"
 #include "Strings_.h"
 #include "SSCP.h"
diff --git a/dwtools/manual_dwtools.cpp b/dwtools/manual_dwtools.cpp
index c9a8747..d81bbfd 100644
--- a/dwtools/manual_dwtools.cpp
+++ b/dwtools/manual_dwtools.cpp
@@ -316,7 +316,7 @@ NORMAL (U"An object of type Categories represents an ordered collection of categ
 ENTRY (U"Categories commands")
 NORMAL (U"Creation:")
 LIST_ITEM (U"\\bu ##Create an empty Categories#")
-LIST_ITEM (U"\\bu @@FFNet & Pattern: To Categories...@")
+LIST_ITEM (U"\\bu @@FFNet & PatternList: To Categories...@")
 NORMAL (U"Viewing and editing:")
 LIST_ITEM (U"\\bu @CategoriesEditor")
 NORMAL (U"Analysis:")
@@ -1769,9 +1769,9 @@ ENTRY (U"Algorithm")
 NORMAL (U"See @@SSCP: Get confidence ellipse area...")
 MAN_END
 
-MAN_BEGIN (U"Discriminant & Pattern: To Categories...", U"djmw", 20040422)
+MAN_BEGIN (U"Discriminant & PatternList: To Categories...", U"djmw", 20040422)
 INTRO (U"A command to use the selected @Discriminant to classify each pattern from the "
-	"selected @Pattern into a category.")
+	"selected @PatternList into a category.")
 NORMAL (U"Arguments as in @@Discriminant & TableOfReal: To ClassificationTable... at .")
 MAN_END
 
@@ -2291,13 +2291,13 @@ INTRO (U"You can choose this command after selecting two objects of type @Excita
 NORMAL (U"A new object is created that contains the second object appended after the first.")
 MAN_END
 
-MAN_BEGIN (U"Excitations: To Pattern...", U"djmw", 19960918)
-INTRO (U"A command to convert every selected @Excitations to a @Pattern object.")
+MAN_BEGIN (U"Excitations: To PatternList...", U"djmw", 19960918)
+INTRO (U"A command to convert every selected @Excitations to a @PatternList object.")
 ENTRY (U"Setting")
 TAG (U"##Join")
-DEFINITION (U"the number of subsequent @Excitation objects to combine into one row of @Pattern. "
-	"E.g. if an #Excitation has length 26 and %join = 2 then each row of #Pattern "
-	"contains 52 elements. The number of rows in #Pattern will be %%my size% / 2. "
+DEFINITION (U"the number of subsequent @Excitation objects to combine into one row of @PatternList. "
+	"E.g. if an #Excitation has length 26 and %join = 2 then each row of #PatternList "
+	"contains 52 elements. The number of rows in #PatternList will be %%my size% / 2. "
 	"In the conversion process the elements of an #Excitation will be divided by 100.0 in order "
 	"to guarantee that all patterns have values between 0 and 1.")
 MAN_END
@@ -2587,18 +2587,18 @@ NORMAL (U"An object of type MSpline represents a linear combination of basis "
 FORMULA (U"MSpline (%x) = \\Si__%k=1..%numberOfCoefficients_ %c__%k_ %mspline__%k_(%x)")
 MAN_END
 
-MAN_BEGIN (U"Pattern", U"djmw", 20041201)
+MAN_BEGIN (U"PatternList", U"djmw", 20160524)
 INTRO (U"One of the @@types of objects@ in P\\s{RAAT}.")
-INTRO (U"An object of type Pattern represents a sequence of patterns that can serve as "
-	"inputs for a neural net. All elements in a Pattern have to be in the interval [0,1].")
-ENTRY (U"Pattern commands")
+INTRO (U"An object of type PatternList is a list of patterns that can serve as "
+	"inputs for a neural net. All elements in a PatternList have to be in the interval [0,1].")
+ENTRY (U"PatternList commands")
 NORMAL (U"Creation:")
-LIST_ITEM (U"\\bu ##Create Pattern with zeroes...#")
-LIST_ITEM (U"\\bu @@TableOfReal: To Pattern and Categories...@")
+LIST_ITEM (U"\\bu ##Create PatternList with zeroes...#")
+LIST_ITEM (U"\\bu @@TableOfReal: To PatternList and Categories...@")
 NORMAL (U"Synthesis:")
-LIST_ITEM (U"\\bu @@FFNet & Pattern: To Categories...@")
-LIST_ITEM (U"\\bu @@Pattern & Categories: To FFNet...@")
-ENTRY (U"Inside a Pattern")
+LIST_ITEM (U"\\bu @@FFNet & PatternList: To Categories...@")
+LIST_ITEM (U"\\bu @@PatternList & Categories: To FFNet...@")
+ENTRY (U"Inside a PatternList")
 NORMAL (U"With @Inspect you will see that this type contains the same "
 	"attributes as a @Matrix.")
 MAN_END
@@ -4766,9 +4766,9 @@ NORMAL (U"where %x__%mn_ is the element %m in column %n and %x\\-^__%n_ "
 	"is the mean of column %n.")
 MAN_END
 
-MAN_BEGIN (U"TableOfReal: To Pattern and Categories...", U"djmw", 20040429)
-INTRO (U"Extracts a @Pattern and a @Categories from the selected @TableOfReal.")
-NORMAL (U"The selected rows and columns are copied into the Pattern and "
+MAN_BEGIN (U"TableOfReal: To PatternList and Categories...", U"djmw", 20040429)
+INTRO (U"Extracts a @PatternList and a @Categories from the selected @TableOfReal.")
+NORMAL (U"The selected rows and columns are copied into the PatternList and "
 	"the corresponding row labels are copied into a Categories. ")
 MAN_END
 
diff --git a/dwtools/praat_David_init.cpp b/dwtools/praat_David_init.cpp
index 8dbe857..514d9fd 100644
--- a/dwtools/praat_David_init.cpp
+++ b/dwtools/praat_David_init.cpp
@@ -68,7 +68,7 @@
 #include "NUMlapack.h"
 #include "NUMmachar.h"
 
-#include "Activation.h"
+#include "ActivationList.h"
 #include "Categories.h"
 #include "CategoriesEditor.h"
 #include "ClassificationTable.h"
@@ -100,7 +100,7 @@
 #include "KlattTable.h"
 #include "Ltas_extensions.h"
 #include "Minimizers.h"
-#include "Pattern.h"
+#include "PatternList.h"
 #include "PCA.h"
 #include "PitchTierEditor.h"
 #include "Polygon_extensions.h"
@@ -122,7 +122,7 @@
 #include "CCA_and_Correlation.h"
 #include "Cepstrum_and_Spectrum.h"
 #include "CCs_to_DTW.h"
-#include "Discriminant_Pattern_Categories.h"
+#include "Discriminant_PatternList_Categories.h"
 #include "DTW_and_TextGrid.h"
 #include "Permutation_and_Index.h"
 #include "Pitch_extensions.h"
@@ -157,7 +157,7 @@ void praat_EditDistanceTable_as_TableOfReal_init (ClassInfo klas);
 
 /********************** Activation *******************************************/
 
-FORM (Activation_formula, U"Activation: Formula", nullptr)
+FORM (ActivationList_formula, U"ActivationList: Formula", nullptr)
 	LABEL (U"label", U"for col := 1 to ncol do { self [row, col] := `formula' ; x := x + dx } y := y + dy }}")
 	TEXTFIELD (U"formula", U"self")
 	OK
@@ -165,10 +165,18 @@ DO
 	praat_Fon_formula (dia, interpreter);
 END
 
-DIRECT (Activation_to_Matrix)
+DIRECT (ActivationList_to_Matrix)
 	LOOP {
-		iam (Activation);
-		autoMatrix thee = Activation_to_Matrix (me);
+		iam (ActivationList);
+		autoMatrix thee = ActivationList_to_Matrix (me);
+		praat_new (thee.move(), my name);
+	}
+END
+
+DIRECT (ActivationList_to_PatternList)
+	LOOP {
+		iam (ActivationList);
+		autoPatternList thee = ActivationList_to_PatternList (me);
 		praat_new (thee.move(), my name);
 	}
 END
@@ -1300,14 +1308,14 @@ DIRECT (Discriminant_setGroupLabels)
 	praat_dataChanged (me);
 END
 
-FORM (Discriminant_and_Pattern_to_Categories, U"Discriminant & Pattern: To Categories", U"Discriminant & Pattern: To Categories...")
+FORM (Discriminant_and_PatternList_to_Categories, U"Discriminant & PatternList: To Categories", U"Discriminant & PatternList: To Categories...")
 	BOOLEAN (U"Pool covariance matrices", true)
 	BOOLEAN (U"Use apriori probabilities", true)
 	OK
 DO
 	Discriminant me = FIRST (Discriminant);
-	Pattern pat = FIRST (Pattern);
-	autoCategories thee = Discriminant_and_Pattern_to_Categories (me, pat, GET_INTEGER (U"Pool covariance matrices"), GET_INTEGER (U"Use apriori probabilities"));
+	PatternList pat = FIRST (PatternList);
+	autoCategories thee = Discriminant_and_PatternList_to_Categories (me, pat, GET_INTEGER (U"Pool covariance matrices"), GET_INTEGER (U"Use apriori probabilities"));
 	praat_new (thee.move(), my name, U"_", pat->name);
 END
 
@@ -2976,13 +2984,13 @@ DIRECT (ExcitationList_append)
 	praat_new (result.move(), U"appended");
 END
 
-FORM (ExcitationList_to_Pattern, U"Excitations: To Pattern", nullptr)
+FORM (ExcitationList_to_PatternList, U"Excitations: To PatternList", nullptr)
 	NATURAL (U"Join", U"1")
 	OK
 DO
 	LOOP {
 		iam (ExcitationList);
-		autoPattern result = ExcitationList_to_Pattern (me, GET_INTEGER (U"Join"));
+		autoPatternList result = ExcitationList_to_PatternList (me, GET_INTEGER (U"Join"));
 		praat_new (result.move(), my name);
 	}
 END
@@ -4354,10 +4362,10 @@ DO
 	}
 END
 
-DIRECT (Matrix_to_Activation)
+DIRECT (Matrix_to_ActivationList)
 	LOOP {
 		iam (Matrix);
-		autoActivation thee = Matrix_to_Activation (me);
+		autoActivationList thee = Matrix_to_ActivationList (me);
 		praat_new (thee.move(), my name);
 	}
 END
@@ -4380,13 +4388,13 @@ DO
 	praat_new (thee.move(), m1->name, U"_", m2->name);
 END
 
-FORM (Matrix_to_Pattern, U"Matrix: To Pattern", nullptr)
+FORM (Matrix_to_PatternList, U"Matrix: To PatternList", nullptr)
 	NATURAL (U"Join", U"1")
 	OK
 DO
 	LOOP {
 		iam (Matrix);
-		praat_new (Matrix_to_Pattern (me, GET_INTEGER (U"Join")), my name);
+		praat_new (Matrix_to_PatternList (me, GET_INTEGER (U"Join")), my name);
 	}
 END
 
@@ -4868,17 +4876,17 @@ END
 
 DIRECT (MSpline_help) Melder_help (U"MSpline"); END
 
-/********************** Pattern *******************************************/
+/********************** PatternList *******************************************/
 
-DIRECT (Pattern_and_Categories_to_Discriminant)
-	Pattern me = FIRST (Pattern);
+DIRECT (PatternList_and_Categories_to_Discriminant)
+	PatternList me = FIRST (PatternList);
 	Categories cat = FIRST (Categories);
-	autoDiscriminant thee = Pattern_and_Categories_to_Discriminant (me, cat);
+	autoDiscriminant thee = PatternList_and_Categories_to_Discriminant (me, cat);
 	praat_new (thee.move(), my name, U"_", cat -> name);
 END
 
-FORM (Pattern_draw, U"Pattern: Draw", 0)
-	NATURAL (U"Pattern number", U"1")
+FORM (PatternList_draw, U"PatternList: Draw", 0)
+	NATURAL (U"PatternList number", U"1")
 	REAL (U"left Horizontal range", U"0.0")
 	REAL (U"right Horizontal range", U"0.0")
 	REAL (U"left Vertical range", U"0.0")
@@ -4888,14 +4896,40 @@ FORM (Pattern_draw, U"Pattern: Draw", 0)
 DO
 	autoPraatPicture picture;
 	LOOP {
-		iam (Pattern);
-		Pattern_draw (me, GRAPHICS, GET_INTEGER (U"Pattern number"),
+		iam (PatternList);
+		PatternList_draw (me, GRAPHICS, GET_INTEGER (U"PatternList number"),
 			GET_REAL (U"left Horizontal range"), GET_REAL (U"right Horizontal range"),
 			GET_REAL (U"left Vertical range"), GET_REAL (U"right Vertical range"), GET_INTEGER (U"Garnish"));
 	}
 END
 
-FORM (Pattern_formula, U"Pattern: Formula", nullptr)
+DIRECT (PatternList_getNumberOfPatterns)
+	LOOP {
+		iam (PatternList);
+		Melder_information (my ny);
+	}
+END
+
+DIRECT (PatternList_getPatternSize)
+	LOOP {
+		iam (PatternList);
+		Melder_information (my nx);
+	}
+END
+
+FORM (PatternList_getValue, U"", nullptr)
+	NATURAL (U"Pattern number", U"1")
+	NATURAL (U"Node number", U"2")
+	OK
+DO
+	long row = GET_INTEGER (U"Pattern number"), col = GET_INTEGER (U"Node number");
+	LOOP {
+		iam (PatternList);
+		Melder_information (row <= my ny && col <= my nx ? my z[row][col] : NUMundefined);
+	}
+END
+
+FORM (PatternList_formula, U"PatternList: Formula", nullptr)
 	LABEL (U"label", U"        y := 1; for row := 1 to nrow do { x := 1; "
 		"for col := 1 to ncol do { self [row, col] := `formula' ; x := x + 1 } "
 		"y := y + 1 }}")
@@ -4905,14 +4939,14 @@ DO
 	praat_Fon_formula (dia, interpreter);
 END
 
-FORM (Pattern_setValue, U"Pattern: Set value", U"Pattern: Set value...")
+FORM (PatternList_setValue, U"PatternList: Set value", U"PatternList: Set value...")
 	NATURAL (U"Row number", U"1")
 	NATURAL (U"Column number", U"1")
 	REAL (U"New value", U"0.0")
 	OK
 DO
 	LOOP {
-		iam (Pattern);
+		iam (PatternList);
 		long row = GET_INTEGER (U"Row number"), column = GET_INTEGER (U"Column number");
 		if (row > my ny) {
 			Melder_throw (U"Row number must not be greater than number of rows.");
@@ -4925,10 +4959,10 @@ DO
 	}
 END
 
-DIRECT (Pattern_to_Matrix)
+DIRECT (PatternList_to_Matrix)
 	LOOP {
-		iam (Pattern);
-		praat_new (Pattern_to_Matrix (me), my name);
+		iam (PatternList);
+		praat_new (PatternList_to_Matrix (me), my name);
 	}
 END
 
@@ -5590,11 +5624,11 @@ END
 
 DIRECT (Polynomial_help) Melder_help (U"Polynomial"); END
 
-FORM (Polynomial_create, U"Create Polynomial", U"Create Polynomial...")
+FORM (Polynomial_create, U"Create Polynomial from coefficients", U"Create Polynomial...")
 	WORD (U"Name", U"p")
 	LABEL (U"", U"Domain of polynomial")
-	REAL (U"Xmin", U"-3")
-	REAL (U"Xmax", U"4")
+	REAL (U"Xmin", U"-3.0")
+	REAL (U"Xmax", U"4.0")
 	LABEL (U"", U"p(x) = c[1] + c[2] x + ... c[n+1] x^n")
 	SENTENCE (U"Coefficients", U"2.0 -1.0 -2.0 1.0")
 	OK
@@ -5606,6 +5640,51 @@ DO
 	praat_new (Polynomial_createFromString (xmin, xmax, GET_STRING (U"Coefficients")), GET_STRING (U"Name"));
 END
 
+FORM (Polynomial_createFromProducts, U"Create Polynomial from second order products", nullptr)
+	WORD (U"Name", U"p")
+	LABEL (U"", U"Domain of polynomial")
+	REAL (U"Xmin", U"-2.0")
+	REAL (U"Xmax", U"2.0")
+	LABEL (U"", U"(1+a[1]*x+x^2)*(1+a[2]*x+x^2)*...*(1+a[n]*x+x^2)")
+	SENTENCE (U"The a's", U"1.0 2.0")
+	OK
+DO
+	double xmin = GET_REAL (U"Xmin"), xmax = GET_REAL (U"Xmax");
+	if (xmin >= xmax) {
+		Melder_throw (U"Xmin must be smaller than Xmax.");
+	}
+	autoPolynomial thee = Polynomial_createFromProductOfSecondOrderTermsString (xmin, xmax, GET_STRING (U"The a's"));
+	praat_new (thee.move(), GET_STRING (U"Name"));
+END
+
+FORM (Polynomial_createFromZeros, U"Create Polynomial from first order products", nullptr)
+	WORD (U"Name", U"p")
+	LABEL (U"", U"Domain of polynomial")
+	REAL (U"Xmin", U"-3.0")
+	REAL (U"Xmax", U"3.0")
+	LABEL (U"", U"(P(x) = (x-zero[1])*(1-zero[2])*...*(x-zero[n])")
+	SENTENCE (U"The zero's", U"1.0 2.0")
+	OK
+DO
+	double xmin = GET_REAL (U"Xmin"), xmax = GET_REAL (U"Xmax");
+	if (xmin >= xmax) {
+		Melder_throw (U"Xmin must be smaller than Xmax.");
+	}
+	autoPolynomial thee = Polynomial_createFromRealRootsString (xmin, xmax, GET_STRING (U"The zero's"));
+	praat_new (thee.move(), GET_STRING (U"Name"));
+END
+
+FORM (Polynomial_divide_secondOrderFactor, U"Polynomial: Divide second order factor", nullptr)
+	LABEL (U"", U"P(x) / (x^2 - factor)")
+	REAL (U"Factor", U"1.0")
+	OK
+DO
+	LOOP {
+		iam (Polynomial);
+		Polynomial_divide_secondOrderFactor (me, GET_REAL (U"Factor"));
+	}
+END
+
 FORM (Polynomial_getArea, U"Polynomial: Get area", U"Polynomial: Get area...")
 	LABEL (U"", U"Interval")
 	REAL (U"Xmin", U"0.0")
@@ -5619,6 +5698,50 @@ DO
 	}
 END
 
+FORM (Polynomial_getRemainder, U"", 0)
+	REAL (U"Monomial factor", U"1.0")
+	OK
+DO
+	LOOP {
+		iam (Polynomial);
+		double remainder;
+		autoPolynomial p = Data_copy (me);
+		Polynomial_divide_firstOrderFactor (p.get(), GET_REAL (U"Monomial factor"), & remainder);
+		Melder_information (remainder);
+	}
+END
+
+FORM (Polynomial_getDerivativesAtX, U"Polynomial: Get derivatives at X", nullptr)
+	REAL (U"X", U"0.5")
+	INTEGER (U"Number of derivatives", U"2")
+	OK
+DO
+	long numberOfDerivatives = GET_INTEGER (U"Number of derivatives");
+	autoNUMvector<double> derivatives (0L, numberOfDerivatives);
+	LOOP {
+		iam (Polynomial);
+		Polynomial_evaluateDerivatives (me, GET_REAL (U"X"), derivatives.peek(), numberOfDerivatives);
+		MelderInfo_open ();
+		for (long i = 0; i <= numberOfDerivatives; i++) {
+			MelderInfo_writeLine (i, U": ", i < my numberOfCoefficients ? derivatives [i] : NUMundefined);
+		}
+		MelderInfo_close ();
+	}
+END
+
+FORM (Polynomial_getOneRealRoot, U"Polynomial: Get one real root", nullptr)
+	LABEL (U"", U"Interval: ")
+	REAL (U"left X Range", U"-1.0")
+	REAL (U"right X Range", U"1.0")
+	OK
+DO
+	LOOP {
+		iam (Polynomial);
+		double root = Polynomial_findOneSimpleRealRoot_nr (me, GET_REAL (U"left X Range"), GET_REAL (U"right X Range"));
+		Melder_information (root);
+	}
+END
+
 DIRECT (Polynomial_getDerivative)
 	LOOP {
 		iam (Polynomial);
@@ -5626,10 +5749,13 @@ DIRECT (Polynomial_getDerivative)
 	}
 END
 
-DIRECT (Polynomial_getPrimitive)
+FORM (Polynomial_getPrimitive, U"Polynomial: Get primitive", nullptr)
+	REAL (U"Constant", U"0.0")
+	OK
+DO
 	LOOP {
 		iam (Polynomial);
-		praat_new (Polynomial_getPrimitive (me), my name, U"_primitive");
+		praat_new (Polynomial_getPrimitive (me, GET_REAL (U"Constant")), my name, U"_primitive");
 	}
 END
 
@@ -8374,7 +8500,7 @@ FORM (TableOfReal_choleskyDecomposition, U"TableOfReal: Cholesky decomposition",
 	}
 END
 
-FORM (TableOfReal_to_Pattern_and_Categories, U"TableOfReal: To Pattern and Categories", U"TableOfReal: To Pattern and Categories...")
+FORM (TableOfReal_to_PatternList_and_Categories, U"TableOfReal: To PatternList and Categories", U"TableOfReal: To PatternList and Categories...")
 	INTEGER (U"left Row range", U"0")
 	INTEGER (U"right Row range", U"0 (=all)")
 	INTEGER (U"left Column range", U"0")
@@ -8383,9 +8509,9 @@ FORM (TableOfReal_to_Pattern_and_Categories, U"TableOfReal: To Pattern and Categ
 	DO
 	LOOP {
 		iam (TableOfReal);
-		autoPattern ap; 
+		autoPatternList ap; 
 		autoCategories ac;
-		TableOfReal_to_Pattern_and_Categories (me, GET_INTEGER (U"left Row range"),
+		TableOfReal_to_PatternList_and_Categories (me, GET_INTEGER (U"left Row range"),
 		GET_INTEGER (U"right Row range"), GET_INTEGER (U"left Column range"),
 		GET_INTEGER (U"right Column range"), & ap, & ac);
 		praat_new (ap.move(), Thing_getName (me));
@@ -8724,6 +8850,14 @@ void praat_BandFilterSpectrogram_query_init (ClassInfo klas) {
 	praat_addAction1 (klas, 1, U"Get value in cell...", nullptr, 1, DO_BandFilterSpectrogram_getValueInCell);
 }
 
+void praat_PatternList_query_init (ClassInfo klas) {
+	praat_addAction1 (klas, 0, QUERY_BUTTON, nullptr, 0, 0);
+	praat_addAction1 (klas, 1, U"Get number of patterns", nullptr, 1, DO_PatternList_getNumberOfPatterns);
+	praat_addAction1 (klas, 1, U"Get pattern size", nullptr, 1, DO_PatternList_getPatternSize);
+	praat_addAction1 (klas, 1, U"Get value...", nullptr, 1, DO_PatternList_getValue);
+
+}
+
 static void praat_Spline_init (ClassInfo klas) {
 	praat_FunctionTerms_init (klas);
 	praat_addAction1 (klas, 0, U"Draw knots...", U"Draw basis function...", 1, DO_Spline_drawKnots);
@@ -8800,7 +8934,7 @@ void praat_uvafon_David_init () {
 	Data_recognizeFileType (TextGrid_TIMITLabelFileRecognizer);
 	Data_recognizeFileType (cmuAudioFileRecognizer);
 	
-	Thing_recognizeClassesByName (classActivation, classBarkFilter, classBarkSpectrogram,
+	Thing_recognizeClassesByName (classActivationList, classBarkFilter, classBarkSpectrogram,
 		classCategories, classCepstrum, classCCA,
 		classChebyshevSeries, classClassificationTable, classComplexSpectrogram, classConfusion,
 		classCorrelation, classCovariance, classDiscriminant, classDTW,
@@ -8808,10 +8942,12 @@ void praat_uvafon_David_init () {
 		classFileInMemory, classFileInMemorySet, classFormantFilter,
 		classIndex, classKlattTable,
 		classPermutation, classISpline, classLegendreSeries,
-		classMelFilter, classMelSpectrogram, classMSpline, classPattern, classPCA, classPolynomial, classRoots,
+		classMelFilter, classMelSpectrogram, classMSpline, classPatternList, classPCA, classPolynomial, classRoots,
 		classSimpleString, classStringsIndex, classSpeechSynthesizer, classSPINET, classSSCP,
 		classSVD, nullptr);
 	Thing_recognizeClassByOtherName (classExcitationList, U"Excitations");
+	Thing_recognizeClassByOtherName (classActivationList, U"Activation");
+	Thing_recognizeClassByOtherName (classPatternList, U"Pattern");
 	Thing_recognizeClassByOtherName (classFileInMemorySet, U"FilesInMemory");
 
 	VowelEditor_prefs ();
@@ -8826,6 +8962,8 @@ void praat_uvafon_David_init () {
 	praat_addMenuCommand (U"Objects", U"New", U"Create Permutation...", nullptr, 0, DO_Permutation_create);
 	praat_addMenuCommand (U"Objects", U"New", U"Polynomial", nullptr, 0, nullptr);
 	praat_addMenuCommand (U"Objects", U"New", U"Create Polynomial...", nullptr, 1, DO_Polynomial_create);
+	praat_addMenuCommand (U"Objects", U"New", U"Create Polynomial from product terms...", nullptr, 1, DO_Polynomial_createFromProducts);
+	praat_addMenuCommand (U"Objects", U"New", U"Create Polynomial from real zeros...", nullptr, 1, DO_Polynomial_createFromZeros);
 	praat_addMenuCommand (U"Objects", U"New", U"Create LegendreSeries...", nullptr, 1, DO_LegendreSeries_create);
 	praat_addMenuCommand (U"Objects", U"New", U"Create ChebyshevSeries...", nullptr, 1, DO_ChebyshevSeries_create);
 	praat_addMenuCommand (U"Objects", U"New", U"Create MSpline...", nullptr, 1, DO_MSpline_create);
@@ -8863,12 +9001,13 @@ void praat_uvafon_David_init () {
 	praat_addMenuCommand (U"Objects", U"Open", U"Read Sound from raw 16-bit Big Endian file...", U"Read Sound from raw 16-bit Little Endian file...", 1, DO_Sound_readFromRawFileBE);
 	praat_addMenuCommand (U"Objects", U"Open", U"Read KlattTable from raw text file...", U"Read Matrix from raw text file...", praat_HIDDEN, DO_KlattTable_readFromRawTextFile);
 
-	praat_addAction1 (classActivation, 0, U"Modify", nullptr, 0, nullptr);
-	praat_addAction1 (classActivation, 0, U"Formula...", nullptr, 0, DO_Activation_formula);
-	praat_addAction1 (classActivation, 0, U"Hack", nullptr, 0, nullptr);
-	praat_addAction1 (classActivation, 0, U"To Matrix", nullptr, 0, DO_Activation_to_Matrix);
+	praat_addAction1 (classActivationList, 0, U"Modify", nullptr, 0, nullptr);
+	praat_addAction1 (classActivationList, 0, U"Formula...", nullptr, 0, DO_ActivationList_formula);
+	praat_addAction1 (classActivationList, 0, U"Hack", nullptr, 0, nullptr);
+	praat_addAction1 (classActivationList, 0, U"To Matrix", nullptr, 0, DO_ActivationList_to_Matrix);
+	praat_addAction1 (classActivationList, 0, U"To PatternList", nullptr, 0, DO_ActivationList_to_PatternList);
 
-	praat_addAction2 (classActivation, 1, classCategories, 1, U"To TableOfReal", nullptr, 0, DO_Matrix_Categories_to_TableOfReal);
+	praat_addAction2 (classActivationList, 1, classCategories, 1, U"To TableOfReal", nullptr, 0, DO_Matrix_Categories_to_TableOfReal);
 
 	praat_addAction1 (classBarkFilter, 0, U"BarkFilter help", nullptr, 0, DO_BarkFilter_help);
 	praat_FilterBank_all_init (classBarkFilter);	// deprecated 2014
@@ -9070,7 +9209,7 @@ void praat_uvafon_David_init () {
 	praat_Eigen_Matrix_project (classDiscriminant, classBarkFilter); // deprecated 2014
 	praat_Eigen_Matrix_project (classDiscriminant, classMelFilter); // deprecated 2014
 
-	praat_addAction2 (classDiscriminant, 1, classPattern, 1, U"To Categories...", nullptr, 0, DO_Discriminant_and_Pattern_to_Categories);
+	praat_addAction2 (classDiscriminant, 1, classPatternList, 1, U"To Categories...", nullptr, 0, DO_Discriminant_and_PatternList_to_Categories);
 	praat_addAction2 (classDiscriminant, 1, classSSCP, 1, U"Project", nullptr, 0, DO_Eigen_and_SSCP_project);
 	praat_addAction2 (classDiscriminant, 1, classStrings, 1, U"Modify Discriminant", nullptr, 0, 0);
 	praat_addAction2 (classDiscriminant, 1, classStrings, 1, U"Set group labels", nullptr, 0, DO_Discriminant_setGroupLabels);
@@ -9180,7 +9319,8 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classExcitationList, 0, U"Synthesize", nullptr, 0, 0);
 	praat_addAction1 (classExcitationList, 2, U"Append", nullptr, 0, DO_ExcitationList_append);
 	praat_addAction1 (classExcitationList, 0, U"Convert", nullptr, 0, 0);
-	praat_addAction1 (classExcitationList, 0, U"To Pattern...", nullptr, 0, DO_ExcitationList_to_Pattern);
+	praat_addAction1 (classExcitationList, 0, U"To PatternList...", nullptr, 0, DO_ExcitationList_to_PatternList);
+	praat_addAction1 (classExcitationList, 0, U"To Pattern...", nullptr, praat_HIDDEN, DO_ExcitationList_to_PatternList);
 	praat_addAction1 (classExcitationList, 0, U"To TableOfReal", nullptr, 0, DO_ExcitationList_to_TableOfReal);
 
 	praat_addAction2 (classExcitationList, 1, classExcitation, 0, U"Add to ExcitationList", nullptr, 0, DO_ExcitationList_addItem);
@@ -9249,8 +9389,10 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classMatrix, 0, U"Solve equation...", U"Analyse", 0, DO_Matrix_solveEquation);
 	praat_addAction1 (classMatrix, 0, U"To PCA (by rows)", U"Solve equation...", 0, DO_Matrix_to_PCA_byRows);
 	praat_addAction1 (classMatrix, 0, U"To PCA (by columns)", U"To PCA (by rows)", 0, DO_Matrix_to_PCA_byColumns);
-	praat_addAction1 (classMatrix, 0, U"To Pattern...", U"To VocalTract", 1, DO_Matrix_to_Pattern);
-	praat_addAction1 (classMatrix, 0, U"To Activation", U"To Pattern...", 1, DO_Matrix_to_Activation);
+	praat_addAction1 (classMatrix, 0, U"To PatternList...", U"To VocalTract", 1, DO_Matrix_to_PatternList);
+	praat_addAction1 (classMatrix, 0, U"To Pattern...", U"To VocalTract", praat_HIDDEN, DO_Matrix_to_PatternList);
+	praat_addAction1 (classMatrix, 0, U"To ActivationList", U"To PatternList...", 1, DO_Matrix_to_ActivationList);
+	praat_addAction1 (classMatrix, 0, U"To Activation", U"To PatternList...", praat_HIDDEN, DO_Matrix_to_ActivationList);
 	praat_addAction1 (classMatrix, 2, U"To DTW...", U"To ParamCurve", 1, DO_Matrices_to_DTW);
 
 	praat_addAction2 (classMatrix, 1, classCategories, 1, U"To TableOfReal", nullptr, 0, DO_Matrix_Categories_to_TableOfReal);
@@ -9291,15 +9433,16 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classMSpline, 0, U"MSpline help", nullptr, 0, DO_MSpline_help);
 	praat_Spline_init (classMSpline);
 
-	praat_addAction1 (classPattern, 0, U"Draw", nullptr, 0, 0);
-	praat_addAction1 (classPattern, 0, U"Draw...", nullptr, 0, DO_Pattern_draw);
-	praat_addAction1 (classPattern, 0, MODIFY_BUTTON, nullptr, 0, 0);
-	praat_addAction1 (classPattern, 0, U"Formula...", nullptr, 1, DO_Pattern_formula);
-	praat_addAction1 (classPattern, 0, U"Set value...", nullptr, 1, DO_Pattern_setValue);
-	praat_addAction1 (classPattern, 0, U"To Matrix", nullptr, 0, DO_Pattern_to_Matrix);
+	praat_addAction1 (classPatternList, 0, U"Draw", nullptr, 0, 0);
+	praat_addAction1 (classPatternList, 0, U"Draw...", nullptr, 0, DO_PatternList_draw);
+	praat_PatternList_query_init (classPatternList);
+	praat_addAction1 (classPatternList, 0, MODIFY_BUTTON, nullptr, 0, 0);
+	praat_addAction1 (classPatternList, 0, U"Formula...", nullptr, 1, DO_PatternList_formula);
+	praat_addAction1 (classPatternList, 0, U"Set value...", nullptr, 1, DO_PatternList_setValue);
+	praat_addAction1 (classPatternList, 0, U"To Matrix", nullptr, 0, DO_PatternList_to_Matrix);
 
-	praat_addAction2 (classPattern, 1, classCategories, 1, U"To TableOfReal", nullptr, 0, DO_Matrix_Categories_to_TableOfReal);
-	praat_addAction2 (classPattern, 1, classCategories, 1, U"To Discriminant", nullptr, 0, DO_Pattern_and_Categories_to_Discriminant);
+	praat_addAction2 (classPatternList, 1, classCategories, 1, U"To TableOfReal", nullptr, 0, DO_Matrix_Categories_to_TableOfReal);
+	praat_addAction2 (classPatternList, 1, classCategories, 1, U"To Discriminant", nullptr, 0, DO_PatternList_and_Categories_to_Discriminant);
 
 	praat_addAction1 (classPCA, 0, U"PCA help", nullptr, 0, DO_PCA_help);
 	praat_addAction1 (classPCA, 0, DRAW_BUTTON, nullptr, 0, 0);
@@ -9324,7 +9467,7 @@ void praat_uvafon_David_init () {
 	praat_addAction2 (classPCA, 1, classMatrix, 1, U"To Matrix (pc)...", nullptr, praat_HIDDEN, DO_PCA_and_Matrix_to_Matrix_projectColumns);
 	praat_addAction2 (classPCA, 1, classMatrix, 1, U"To Matrix (project rows)...", nullptr, 0, DO_PCA_and_Matrix_to_Matrix_projectRows);
 	praat_addAction2 (classPCA, 1, classMatrix, 1, U"To Matrix (project columns)...", nullptr, 0, DO_PCA_and_Matrix_to_Matrix_projectColumns);
-	praat_addAction2 (classPCA, 1, classPattern, 1, U"To Matrix (project rows)...", nullptr, 0, DO_PCA_and_Matrix_to_Matrix_projectRows);
+	praat_addAction2 (classPCA, 1, classPatternList, 1, U"To Matrix (project rows)...", nullptr, 0, DO_PCA_and_Matrix_to_Matrix_projectRows);
 	praat_addAction2 (classPCA, 1, classSSCP, 1, U"Project", nullptr, 0, DO_Eigen_and_SSCP_project);
 	praat_addAction2 (classPCA, 1, classTableOfReal, 1, U"To TableOfReal...", nullptr, 0, DO_PCA_and_TableOfReal_to_TableOfReal_projectRows);
 	praat_addAction2 (classPCA, 1, classTableOfReal, 1, U"To TableOfReal (project rows)...", nullptr, 0, DO_PCA_and_TableOfReal_to_TableOfReal_projectRows);
@@ -9389,13 +9532,18 @@ void praat_uvafon_David_init () {
 	praat_FunctionTerms_init (classPolynomial);
 	praat_addAction1 (classPolynomial, 0, U"-- area --", U"Get x of maximum...", 1, 0);
 	praat_addAction1 (classPolynomial, 1, U"Get area...", U"-- area --", 1, DO_Polynomial_getArea);
+	praat_addAction1 (classPolynomial, 1, U"Get remainder...", U"Get area...", 1, DO_Polynomial_getRemainder);
 	praat_addAction1 (classPolynomial, 0, U"-- monic --", U"Set coefficient...", 1, 0);
 	praat_addAction1 (classPolynomial, 0, U"Scale coefficients (monic)", U"-- monic --", 1, DO_Polynomial_scaleCoefficients_monic);
+	praat_addAction1 (classPolynomial, 1, U"Divide (second order factor)...", U"Scale coefficients (monic)", 1, DO_Polynomial_divide_secondOrderFactor);
+	
 	praat_addAction1 (classPolynomial, 1, U"Get value (complex)...", U"Get value...", 1, DO_Polynomial_evaluate_z);
+	praat_addAction1 (classPolynomial, 1, U"Get derivatives at X...", U"Get value (complex)...", 1, DO_Polynomial_getDerivativesAtX);
+	praat_addAction1 (classPolynomial, 1, U"Get one real root...", U"Get derivatives at X...", 1, DO_Polynomial_getOneRealRoot);
 	praat_addAction1 (classPolynomial, 0, U"To Spectrum...", U"Analyse", 0, DO_Polynomial_to_Spectrum);
 	praat_addAction1 (classPolynomial, 0, U"To Roots", nullptr, 0, DO_Polynomial_to_Roots);
 	praat_addAction1 (classPolynomial, 0, U"To Polynomial (derivative)", nullptr, 0, DO_Polynomial_getDerivative);
-	praat_addAction1 (classPolynomial, 0, U"To Polynomial (primitive)", nullptr, 0, DO_Polynomial_getPrimitive);
+	praat_addAction1 (classPolynomial, 0, U"To Polynomial (primitive)...", nullptr, 0, DO_Polynomial_getPrimitive);
 	praat_addAction1 (classPolynomial, 0, U"Scale x...", nullptr, 0, DO_Polynomial_scaleX);
 	praat_addAction1 (classPolynomial, 2, U"Multiply", nullptr, 0, DO_Polynomials_multiply);
 	praat_addAction1 (classPolynomial, 2, U"Divide...", nullptr, 0, DO_Polynomials_divide);
@@ -9569,8 +9717,9 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classTableOfReal, 2, U"-- between tables --", U"To Configuration (lda)...", 1, 0);
 	praat_addAction1 (classTableOfReal, 2, U"To TableOfReal (cross-correlations)...", nullptr, praat_HIDDEN + praat_DEPTH_1, DO_TableOfReal_and_TableOfReal_crossCorrelations);
 
-	praat_addAction1 (classTableOfReal, 1, U"To Pattern and Categories...", U"To Matrix", 1, DO_TableOfReal_to_Pattern_and_Categories);
-	praat_addAction1 (classTableOfReal, 1, U"Split into Pattern and Categories...", U"To Pattern and Categories...", praat_DEPTH_1 | praat_HIDDEN, DO_TableOfReal_to_Pattern_and_Categories);
+	praat_addAction1 (classTableOfReal, 1, U"To PatternList and Categories...", U"To Matrix", 1, DO_TableOfReal_to_PatternList_and_Categories);
+	praat_addAction1 (classTableOfReal, 1, U"To Pattern and Categories...", U"To Matrix", praat_DEPTH_1 | praat_HIDDEN, DO_TableOfReal_to_PatternList_and_Categories);
+	praat_addAction1 (classTableOfReal, 1, U"Split into Pattern and Categories...", U"To Pattern and Categories...", praat_DEPTH_1 | praat_HIDDEN, DO_TableOfReal_to_PatternList_and_Categories);
 	praat_addAction1 (classTableOfReal, 0, U"To Permutation (sort row labels)", U"To Matrix", 1, DO_TableOfReal_to_Permutation_sortRowlabels);
 
 	praat_addAction1 (classTableOfReal, 1, U"To SVD", nullptr, praat_HIDDEN, DO_TableOfReal_to_SVD);
diff --git a/dwtools/praat_HMM_init.cpp b/dwtools/praat_HMM_init.cpp
index b08e64f..d71394c 100644
--- a/dwtools/praat_HMM_init.cpp
+++ b/dwtools/praat_HMM_init.cpp
@@ -337,11 +337,11 @@ END
 
 FORM (HMMObservationSequence_to_HMM, U"HMMObservationSequence: To HMM", nullptr)
 	LABEL (U"", U"(0 states gives a non-hidden model) ")
-	INTEGER (U"Number of states", U"2")
+	INTEGER (U"Number of hidden states", U"2")
 	BOOLEAN (U"Left to right model", false)
 	OK
 DO
-	long numberOfStates = GET_INTEGER (U"Number of states");
+	long numberOfStates = GET_INTEGER (U"Number of hidden states");
 	LOOP {
 		iam (HMMObservationSequence);
 		autoHMM result = HMM_createFromHMMObservationSequence (me,
diff --git a/fon/manual_tutorials.cpp b/fon/manual_tutorials.cpp
index 8fd200b..5bbd1ed 100644
--- a/fon/manual_tutorials.cpp
+++ b/fon/manual_tutorials.cpp
@@ -22,10 +22,12 @@
 void manual_tutorials_init (ManPages me);
 void manual_tutorials_init (ManPages me) {
 
-MAN_BEGIN (U"What's new?", U"ppgb", 20160523)
+MAN_BEGIN (U"What's new?", U"ppgb", 20160613)
 INTRO (U"Latest changes in Praat.")
 //LIST_ITEM (U"• Manual page about @@drawing a vowel triangle at .")
 
+NORMAL (U"##6.0.19# (13 June 2016)")
+LIST_ITEM (U"• Mac: dragging selections repaired for System 10.11.5.")
 NORMAL (U"##6.0.18# (23 May 2016)")
 LIST_ITEM (U"• Windows: better dotted lines.")
 LIST_ITEM (U"• TextGrid window: again better automatic alignment.")
@@ -3722,7 +3724,7 @@ LIST_ITEM (U"• @Artword: articulatory target specifications as functions of ti
 LIST_ITEM (U"• (@VocalTract: area function)")
 NORMAL (U"Neural net package:")
 LIST_ITEM (U"• @FFNet: feed-forward neural net")
-LIST_ITEM (U"• @Pattern")
+LIST_ITEM (U"• @PatternList")
 LIST_ITEM (U"• @Categories: for classification (#CategoriesEditor)")
 NORMAL (U"Numerical and statistical analysis:")
 LIST_ITEM (U"• @Eigen: eigenvectors and eigenvalues")
diff --git a/fon/praat_Fon.cpp b/fon/praat_Fon.cpp
index 869ba6b..39ef9fc 100644
--- a/fon/praat_Fon.cpp
+++ b/fon/praat_Fon.cpp
@@ -5808,7 +5808,7 @@ static bool inited;
 if (! inited) {
 	structMelderDir defaultDir { { 0 } };
 	Melder_getDefaultDir (& defaultDir);
-	char32 *workingDirectory = Melder_dirToPath (& defaultDir);
+	const char32 *workingDirectory = Melder_dirToPath (& defaultDir);
 	char32 path [kMelder_MAXPATH+1];
 	#if defined (UNIX)
 		Melder_sprint (path, kMelder_MAXPATH+1, workingDirectory, U"/*.wav");
@@ -5837,7 +5837,7 @@ static bool inited;
 if (! inited) {
 	structMelderDir defaultDir = { { 0 } };
 	Melder_getDefaultDir (& defaultDir);
-	char32 *workingDirectory = Melder_dirToPath (& defaultDir);
+	const char32 *workingDirectory = Melder_dirToPath (& defaultDir);
 	char32 path [kMelder_MAXPATH+1];
 	#if defined (UNIX)
 		Melder_sprint (path, kMelder_MAXPATH+1, workingDirectory, U"/*");
diff --git a/gram/RBM.cpp b/gram/RBM.cpp
index faea0a2..f2da6c1 100644
--- a/gram/RBM.cpp
+++ b/gram/RBM.cpp
@@ -155,23 +155,23 @@ void RBM_update (RBM me, double learningRate) {
 	}
 }
 
-void RBM_Pattern_applyToInput (RBM me, Pattern thee, long rowNumber) {
+void RBM_PatternList_applyToInput (RBM me, PatternList thee, long rowNumber) {
 	Melder_assert (my numberOfInputNodes == thy nx);
 	for (long ifeature = 1; ifeature <= my numberOfInputNodes; ifeature ++) {
 		my inputActivities [ifeature] = thy z [rowNumber] [ifeature];
 	}
 }
 
-void RBM_Pattern_applyToOutput (RBM me, Pattern thee, long rowNumber) {
+void RBM_PatternList_applyToOutput (RBM me, PatternList thee, long rowNumber) {
 	Melder_assert (my numberOfOutputNodes == thy nx);
 	for (long icat = 1; icat <= my numberOfOutputNodes; icat ++) {
 		my outputActivities [icat] = thy z [rowNumber] [icat];
 	}
 }
 
-void RBM_Pattern_learn (RBM me, Pattern thee, double learningRate) {
+void RBM_PatternList_learn (RBM me, PatternList thee, double learningRate) {
 	for (long ipattern = 1; ipattern <= thy ny; ipattern ++) {
-		RBM_Pattern_applyToInput (me, thee, ipattern);
+		RBM_PatternList_applyToInput (me, thee, ipattern);
 		RBM_spreadUp (me);
 		RBM_sampleOutput (me);
 		RBM_spreadDown_reconstruction (me);
diff --git a/gram/RBM.h b/gram/RBM.h
index eb25baf..20e41d0 100644
--- a/gram/RBM.h
+++ b/gram/RBM.h
@@ -19,7 +19,7 @@
  */
 
 #include "Table.h"
-#include "Pattern.h"
+#include "PatternList.h"
 
 #include "RBM_def.h"
 
@@ -35,9 +35,9 @@ void RBM_sampleInput (RBM me);
 void RBM_sampleOutput (RBM me);
 void RBM_update (RBM me, double learningRate);
 
-void RBM_Pattern_applyToInput (RBM me, Pattern thee, long rowNumber);
-void RBM_Pattern_applyToOutput (RBM me, Pattern thee, long rowNumber);
-void RBM_Pattern_learn (RBM me, Pattern thee, double learningRate);
+void RBM_PatternList_applyToInput (RBM me, PatternList thee, long rowNumber);
+void RBM_PatternList_applyToOutput (RBM me, PatternList thee, long rowNumber);
+void RBM_PatternList_learn (RBM me, PatternList thee, double learningRate);
 
 autoMatrix RBM_extractInputActivities (RBM me);
 autoMatrix RBM_extractOutputActivities (RBM me);
diff --git a/gram/praat_gram.cpp b/gram/praat_gram.cpp
index 4004184..b7a5aaa 100644
--- a/gram/praat_gram.cpp
+++ b/gram/praat_gram.cpp
@@ -1801,33 +1801,33 @@ END2 }
 
 #pragma mark RBM & PATTERN
 
-FORM (RBM_Pattern_applyToInput, U"RBM & Pattern: Apply to input", nullptr) {
+FORM (RBM_PatternList_applyToInput, U"RBM & PatternList: Apply to input", nullptr) {
 	NATURAL (U"Row number", U"1")
 	OK2
 DO
 	iam_ONLY (RBM);
-	thouart_ONLY (Pattern);
-	RBM_Pattern_applyToInput (me, thee, GET_INTEGER (U"Row number"));
+	thouart_ONLY (PatternList);
+	RBM_PatternList_applyToInput (me, thee, GET_INTEGER (U"Row number"));
 	praat_dataChanged (me);
 END2 }
 
-FORM (RBM_Pattern_applyToOutput, U"RBM & Pattern: Apply to output", nullptr) {
+FORM (RBM_PatternList_applyToOutput, U"RBM & PatternList: Apply to output", nullptr) {
 	NATURAL (U"Row number", U"1")
 	OK2
 DO
 	iam_ONLY (RBM);
-	thouart_ONLY (Pattern);
-	RBM_Pattern_applyToOutput (me, thee, GET_INTEGER (U"Row number"));
+	thouart_ONLY (PatternList);
+	RBM_PatternList_applyToOutput (me, thee, GET_INTEGER (U"Row number"));
 	praat_dataChanged (me);
 END2 }
 
-FORM (RBM_Pattern_learn, U"RBM & Pattern: Learn", nullptr) {
+FORM (RBM_PatternList_learn, U"RBM & PatternList: Learn", nullptr) {
 	POSITIVE (U"Learning rate", U"0.001")
 	OK2
 DO
 	iam_ONLY (RBM);
-	thouart_ONLY (Pattern);
-	RBM_Pattern_learn (me, thee, GET_REAL (U"Learning rate"));
+	thouart_ONLY (PatternList);
+	RBM_PatternList_learn (me, thee, GET_REAL (U"Learning rate"));
 	praat_dataChanged (me);
 END2 }
 
@@ -2008,9 +2008,9 @@ void praat_uvafon_gram_init () {
 		praat_addAction1 (classRBM, 0, U"Extract output biases", nullptr, 0, DO_RBM_extractOutputBiases);
 		praat_addAction1 (classRBM, 0, U"Extract weights", nullptr, 0, DO_RBM_extractWeights);
 
-	praat_addAction2 (classRBM, 1, classPattern, 1, U"Apply to input...", nullptr, 0, DO_RBM_Pattern_applyToInput);
-	praat_addAction2 (classRBM, 1, classPattern, 1, U"Apply to output...", nullptr, 0, DO_RBM_Pattern_applyToOutput);
-	praat_addAction2 (classRBM, 1, classPattern, 1, U"Learn...", nullptr, 0, DO_RBM_Pattern_learn);
+	praat_addAction2 (classRBM, 1, classPatternList, 1, U"Apply to input...", nullptr, 0, DO_RBM_PatternList_applyToInput);
+	praat_addAction2 (classRBM, 1, classPatternList, 1, U"Apply to output...", nullptr, 0, DO_RBM_PatternList_applyToOutput);
+	praat_addAction2 (classRBM, 1, classPatternList, 1, U"Learn...", nullptr, 0, DO_RBM_PatternList_learn);
 }
 
 /* End of file praat_gram.cpp */
diff --git a/stat/Table.cpp b/stat/Table.cpp
index 4197458..4f94d74 100644
--- a/stat/Table.cpp
+++ b/stat/Table.cpp
@@ -1335,10 +1335,12 @@ void Table_formula_columnRange (Table me, long fromColumn, long toColumn, const
 					Melder_free (result. result.stringResult);
 				} else if (result. expressionType == kFormula_EXPRESSION_TYPE_NUMERIC) {
 					Table_setNumericValue (me, irow, icol, result. result.numericResult);
-				} else if (result. expressionType == kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY) {
-					Melder_throw (me, U": cannot put arrays into cells.");
+				} else if (result. expressionType == kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR) {
+					Melder_throw (me, U": cannot put vectors into cells.");
+				} else if (result. expressionType == kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX) {
+					Melder_throw (me, U": cannot put matrices into cells.");
 				} else if (result. expressionType == kFormula_EXPRESSION_TYPE_STRING_ARRAY) {
-					Melder_throw (me, U": cannot put arrays into cells.");
+					Melder_throw (me, U": cannot put string arrays into cells.");
 				}
 			}
 		}
diff --git a/sys/Formula.cpp b/sys/Formula.cpp
index b6aee72..d6a4a94 100644
--- a/sys/Formula.cpp
+++ b/sys/Formula.cpp
@@ -43,7 +43,8 @@ static int theLevel = 1;
 static int theExpressionType [1 + MAXIMUM_NUMBER_OF_LEVELS];
 static bool theOptimize;
 
-static struct Formula_NumericArray theZeroNumericArray = { 0, 0, nullptr };
+static struct Formula_NumericVector theZeroNumericVector = { 0, nullptr };
+static struct Formula_NumericMatrix theZeroNumericMatrix = { 0, 0, nullptr };
 
 typedef struct structFormulaInstruction {
 	int symbol;
@@ -136,7 +137,11 @@ enum { GEENSYMBOOL_,
 		DEMO_WINDOW_TITLE_, DEMO_SHOW_, DEMO_WAIT_FOR_INPUT_, DEMO_INPUT_, DEMO_CLICKED_IN_,
 		DEMO_CLICKED_, DEMO_X_, DEMO_Y_, DEMO_KEY_PRESSED_, DEMO_KEY_,
 		DEMO_SHIFT_KEY_PRESSED_, DEMO_COMMAND_KEY_PRESSED_, DEMO_OPTION_KEY_PRESSED_, DEMO_EXTRA_CONTROL_KEY_PRESSED_,
-		ZERO_NUMAR_, LINEAR_NUMAR_, RANDOM_UNIFORM_NUMAR_, RANDOM_INTEGER_NUMAR_, RANDOM_GAUSS_NUMAR_,
+		ZERO_NUMVEC_, ZERO_NUMMAT_,
+		LINEAR_NUMVEC_, LINEAR_NUMMAT_,
+		RANDOM_UNIFORM_NUMVEC_, RANDOM_UNIFORM_NUMMAT_,
+		RANDOM_INTEGER_NUMVEC_, RANDOM_INTEGER_NUMMAT_,
+		RANDOM_GAUSS_NUMVEC_, RANDOM_GAUSS_NUMMAT_,
 		NUMBER_OF_ROWS_, NUMBER_OF_COLUMNS_, EDITOR_, HASH_,
 	#define HIGH_FUNCTION_N  HASH_
 
@@ -171,7 +176,7 @@ enum { GEENSYMBOOL_,
 	GOTO_, IFTRUE_, IFFALSE_, INCREMENT_GREATER_GOTO_,
 	LABEL_,
 	DECREMENT_AND_ASSIGN_, ADD_3DOWN_, POP_2_,
-	NUMERIC_ARRAY_ELEMENT_, VARIABLE_REFERENCE_,
+	NUMERIC_VECTOR_ELEMENT_, NUMERIC_MATRIX_ELEMENT_, VARIABLE_REFERENCE_,
 	SELF0_, SELFSTR0_,
 	OBJECTCELL0_, OBJECTCELLSTR0_, OBJECTCELL1_, OBJECTCELLSTR1_, OBJECTCELL2_, OBJECTCELLSTR2_,
 	OBJECTLOCATION0_, OBJECTLOCATIONSTR0_, OBJECTLOCATION1_, OBJECTLOCATIONSTR1_, OBJECTLOCATION2_, OBJECTLOCATIONSTR2_,
@@ -184,7 +189,8 @@ enum { GEENSYMBOOL_,
 /* Symbols introduced by lexical analysis. */
 
 	STRING_,
-	NUMERIC_VARIABLE_, STRING_VARIABLE_, NUMERIC_ARRAY_VARIABLE_, STRING_ARRAY_VARIABLE_,
+	NUMERIC_VARIABLE_, NUMERIC_VECTOR_VARIABLE_, NUMERIC_MATRIX_VARIABLE_,
+	STRING_VARIABLE_, STRING_ARRAY_VARIABLE_,
 	VARIABLE_NAME_, INDEXED_NUMERIC_VARIABLE_, INDEXED_STRING_VARIABLE_,
 	END_
 	#define hoogsteSymbool END_
@@ -233,7 +239,11 @@ static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
 	U"demoWindowTitle", U"demoShow", U"demoWaitForInput", U"demoInput", U"demoClickedIn",
 	U"demoClicked", U"demoX", U"demoY", U"demoKeyPressed", U"demoKey$",
 	U"demoShiftKeyPressed", U"demoCommandKeyPressed", U"demoOptionKeyPressed", U"demoExtraControlKeyPressed",
-	U"zero#", U"linear#", U"randomUniform#", U"randomInteger#", U"randomGauss#",
+	U"zero#", U"zero##",
+	U"linear#", U"linear##",
+	U"randomUniform#", U"randomUniform##",
+	U"randomInteger#", U"randomInteger##",
+	U"randomGauss#", U"randomGauss##",
 	U"numberOfRows", U"numberOfColumns", U"editor", U"hash",
 
 	U"length", U"number", U"fileReadable",	U"deleteFile", U"createDirectory", U"variableExists",
@@ -249,7 +259,7 @@ static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
 	U"_goto", U"_iftrue", U"_iffalse", U"_incrementGreaterGoto",
 	U"_label",
 	U"_decrementAndAssign", U"_add3Down", U"_pop2",
-	U"_numericArrayElement", U"_variableReference",
+	U"_numericVectorElement", U"_numericMatrixElement", U"_variableReference",
 	U"_self0", U"_self0$",
 	U"_objectcell0", U"_objectcell0$", U"_objectcell1", U"_objectcell1$", U"_objectcell2", U"_objectcell2$",
 	U"_objectlocation0", U"_objectlocation0$", U"_objectlocation1", U"_objectlocation1$", U"_objectlocation2", U"_objectlocation2$",
@@ -259,7 +269,8 @@ static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
 	U"_funktie0", U"_funktie0$", U"_funktie1", U"_funktie1$", U"_funktie2", U"_funktie2$",
 	U"_square",
 	U"_string",
-	U"a numeric variable", U"a string variable", U"a numeric array variable", U"a string array variable",
+	U"a numeric variable", U"a vector variable", U"a matrix variable",
+	U"a string variable", U"a string array variable",
 	U"a variable name", U"an indexed numeric variable", U"an indexed string variable",
 	U"the end of the formula"
 };
@@ -364,7 +375,8 @@ static void Formula_lexan () {
 				((theExpression [ikar + 1] >= U'a' && theExpression [ikar + 1] <= U'z') || theExpression [ikar + 1] >= 192)
 				&& (itok == 0 || (lexan [itok]. symbol != MATRIKS_ && lexan [itok]. symbol != MATRIKSSTR_)))) {
 			int tok;
-			bool isString = false, isArray = false;
+			bool isString = false;
+			int rank = 0;
 			stokaan;
 			do stokkar while ((kar >= U'A' && kar <= U'Z') || (kar >= U'a' && kar <= U'z') || kar >= 192 || (kar >= U'0' && kar <= U'9') || kar == U'_' || kar == U'.');
 			if (kar == '$') {
@@ -372,8 +384,20 @@ static void Formula_lexan () {
 				isString = true;
 			}
 			if (kar == '#') {
+				rank += 1;
 				stokkar
-				isArray = true;
+				if (kar == '#') {
+					rank += 1;
+					stokkar
+					if (kar == '#') {
+						rank += 1;
+						stokkar
+						if (kar == '#') {
+							rank += 1;
+							stokkar
+						}
+					}
+				}
 			}
 			stokuit;
 			oudkar;
@@ -427,18 +451,28 @@ static void Formula_lexan () {
 							lexan [itok]. content.string = Melder_dup_f (token.string);
 							numberOfStringConstants ++;
 						} else {
-							if (isArray) {
+							if (rank == 0) {
+								if (isString) {
+									nieuwtok (STRING_VARIABLE_)
+								} else {
+									nieuwtok (NUMERIC_VARIABLE_)
+								}
+							} else if (rank == 1) {
 								if (isString) {
 									nieuwtok (STRING_ARRAY_VARIABLE_)
 								} else {
-									nieuwtok (NUMERIC_ARRAY_VARIABLE_)
+									nieuwtok (NUMERIC_VECTOR_VARIABLE_)
 								}
-							} else {
+							} else if (rank == 2) {
 								if (isString) {
-									nieuwtok (STRING_VARIABLE_)
+									formulefout (U"String matrices not implemented.", ikar);
 								} else {
-									nieuwtok (NUMERIC_VARIABLE_)
+									nieuwtok (NUMERIC_MATRIX_VARIABLE_)
 								}
+							} else if (rank == 3) {
+								formulefout (U"Rank-3 tensors not implemented.", ikar);
+							} else {
+								formulefout (U"Rank-4 tensors not implemented.", ikar);
 							}
 							lexan [itok]. content.variable = var;
 						}
@@ -480,7 +514,7 @@ static void Formula_lexan () {
 						 */
 						int jkar = ikar + 1;
 						while (theExpression [jkar] == U' ' || theExpression [jkar] == U'\t') jkar ++;
-						if (theExpression [jkar] == U'[' && ! isArray) {
+						if (theExpression [jkar] == U'[' && rank == 0) {
 							if (isString) {
 								nieuwtok (INDEXED_STRING_VARIABLE_)
 							} else {
@@ -495,18 +529,28 @@ static void Formula_lexan () {
 								lexan [itok]. content.string = Melder_dup_f (token.string);
 								numberOfStringConstants ++;
 							} else {
-								if (isArray) {
+								if (rank == 0) {
+									if (isString) {
+										nieuwtok (STRING_VARIABLE_)
+									} else {
+										nieuwtok (NUMERIC_VARIABLE_)
+									}
+								} else if (rank == 1) {
 									if (isString) {
 										nieuwtok (STRING_ARRAY_VARIABLE_)
 									} else {
-										nieuwtok (NUMERIC_ARRAY_VARIABLE_)
+										nieuwtok (NUMERIC_VECTOR_VARIABLE_)
 									}
-								} else {
+								} else if (rank == 2) {
 									if (isString) {
-										nieuwtok (STRING_VARIABLE_)
+										formulefout (U"String matrices not implemented.", ikar);
 									} else {
-										nieuwtok (NUMERIC_VARIABLE_)
+										nieuwtok (NUMERIC_MATRIX_VARIABLE_)
 									}
+								} else if (rank == 3) {
+									formulefout (U"Rank-3 tensors not implemented.", ikar);
+								} else {
+									formulefout (U"Rank-4 tensors not implemented.", ikar);
 								}
 								lexan [itok]. content.variable = var;
 							}
@@ -524,7 +568,7 @@ static void Formula_lexan () {
 				if (theExpression [jkar] == U'(' || theExpression [jkar] == U':') {
 					Melder_throw (
 						U"Unknown function " U_LEFT_GUILLEMET, token.string, U_RIGHT_GUILLEMET U" in formula.");
-				} else if (theExpression [jkar] == '[' && ! isArray) {
+				} else if (theExpression [jkar] == '[' && rank == 0) {
 					if (isString) {
 						nieuwtok (INDEXED_STRING_VARIABLE_)
 					} else {
@@ -539,18 +583,28 @@ static void Formula_lexan () {
 						lexan [itok]. content.string = Melder_dup_f (token.string);
 						numberOfStringConstants ++;
 					} else {
-						if (isArray) {
+						if (rank == 0) {
+							if (isString) {
+								nieuwtok (STRING_VARIABLE_)
+							} else {
+								nieuwtok (NUMERIC_VARIABLE_)
+							}
+						} else if (rank == 1) {
 							if (isString) {
 								nieuwtok (STRING_ARRAY_VARIABLE_)
 							} else {
-								nieuwtok (NUMERIC_ARRAY_VARIABLE_)
+								nieuwtok (NUMERIC_VECTOR_VARIABLE_)
 							}
-						} else {
+						} else if (rank == 2) {
 							if (isString) {
-								nieuwtok (STRING_VARIABLE_)
+								formulefout (U"String matrices not implemented.", ikar);
 							} else {
-								nieuwtok (NUMERIC_VARIABLE_)
+								nieuwtok (NUMERIC_MATRIX_VARIABLE_)
 							}
+						} else if (rank == 3) {
+							formulefout (U"Rank-3 tensors not implemented.", ikar);
+						} else {
+							formulefout (U"Rank-4 tensors not implemented.", ikar);
 						}
 						lexan [itok]. content.variable = var;
 					}
@@ -807,26 +861,31 @@ static void parsePowerFactor () {
 		return;
 	}
 
-	if (symbol == NUMERIC_ARRAY_VARIABLE_) {
-		InterpreterVariable var = lexan [ilexan]. content.variable;   // Save before incrementing ilexan.
+	if (symbol == NUMERIC_VECTOR_VARIABLE_) {
+		InterpreterVariable var = lexan [ilexan]. content.variable;   // save before incrementing ilexan
 		if (nieuwlees == RECHTEHAAKOPENEN_) {
-			int n = 0;
-			if (nieuwlees != RECHTEHAAKSLUITEN_) {
-				oudlees;
-				parseExpression ();
-				n ++;
-				while (nieuwlees == KOMMA_) {
-					parseExpression ();
-					n ++;
-				}
-				oudlees;
-				pas (RECHTEHAAKSLUITEN_);
-			}
-			nieuwontleed (NUMBER_); parsenumber (n);
-			nieuwontleed (NUMERIC_ARRAY_ELEMENT_);
+			parseExpression ();
+			pas (RECHTEHAAKSLUITEN_);
+			nieuwontleed (NUMERIC_VECTOR_ELEMENT_);
+		} else {
+			oudlees;
+			nieuwontleed (NUMERIC_VECTOR_VARIABLE_);
+		}
+		parse [iparse]. content.variable = var;
+		return;
+	}
+
+	if (symbol == NUMERIC_MATRIX_VARIABLE_) {
+		InterpreterVariable var = lexan [ilexan]. content.variable;   // save before incrementing ilexan
+		if (nieuwlees == RECHTEHAAKOPENEN_) {
+			parseExpression ();
+			pas (KOMMA_);
+			parseExpression ();
+			pas (RECHTEHAAKSLUITEN_);
+			nieuwontleed (NUMERIC_MATRIX_ELEMENT_);
 		} else {
 			oudlees;
-			nieuwontleed (NUMERIC_ARRAY_VARIABLE_);
+			nieuwontleed (NUMERIC_MATRIX_VARIABLE_);
 		}
 		parse [iparse]. content.variable = var;
 		return;
@@ -1833,15 +1892,11 @@ void Formula_compile (Interpreter interpreter, Daata data, const char32 *express
 			theLocalInterpreter = Interpreter_create (nullptr, nullptr);
 		}
 		theInterpreter = theLocalInterpreter.get();
-		#if USE_HASH
-		for (std::unordered_map<std::u32string, InterpreterVariable>::iterator it = theInterpreter -> variablesMap -> begin(); it != theInterpreter -> variablesMap -> end(); it ++) {
+		for (std::unordered_map<std::u32string, InterpreterVariable>::iterator it = theInterpreter -> variablesMap. begin(); it != theInterpreter -> variablesMap. end(); it ++) {
 			InterpreterVariable var = it -> second;
 			forget (var);
 		}
-		theInterpreter -> variablesMap -> clear ();
-		#else
-		theInterpreter -> variables. removeAllItems ();
-		#endif
+		theInterpreter -> variablesMap. clear ();
 	}
 	theSource = data;
 	theExpression = expression;
@@ -1891,9 +1946,12 @@ static int programPointer;
 static void Stackel_cleanUp (Stackel me) {
 	if (my which == Stackel_STRING) {
 		Melder_free (my string);
-	} else if (my which == Stackel_NUMERIC_ARRAY) {
-		NUMmatrix_free (my numericArray.data, 1, 1);
-		my numericArray = theZeroNumericArray;
+	} else if (my which == Stackel_NUMERIC_VECTOR) {
+		NUMvector_free (my numericVector.data, 1);
+		my numericVector = theZeroNumericVector;
+	} else if (my which == Stackel_NUMERIC_MATRIX) {
+		NUMmatrix_free (my numericMatrix.data, 1, 1);
+		my numericMatrix = theZeroNumericMatrix;
 	}
 }
 static Stackel theStack;
@@ -1914,21 +1972,29 @@ static inline void pushNumber (double x) {
 	stackel -> which = Stackel_NUMBER;
 	stackel -> number = x;
 }
-static void pushString (char32 *x) {
+static void pushNumericVector (long numberOfElements, double *x) {
 	Stackel stackel = & theStack [++ w];
 	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
 	if (w > wmax) wmax ++;
-	stackel -> which = Stackel_STRING;
-	stackel -> string = x;
+	stackel -> which = Stackel_NUMERIC_VECTOR;
+	stackel -> numericVector.numberOfElements = numberOfElements;
+	stackel -> numericVector.data = x;
 }
-static void pushNumericArray (long numberOfRows, long numberOfColumns, double **x) {
+static void pushNumericMatrix (long numberOfRows, long numberOfColumns, double **x) {
 	Stackel stackel = & theStack [++ w];
 	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
 	if (w > wmax) wmax ++;
-	stackel -> which = Stackel_NUMERIC_ARRAY;
-	stackel -> numericArray.numberOfRows = numberOfRows;
-	stackel -> numericArray.numberOfColumns = numberOfColumns;
-	stackel -> numericArray.data = x;
+	stackel -> which = Stackel_NUMERIC_MATRIX;
+	stackel -> numericMatrix.numberOfRows = numberOfRows;
+	stackel -> numericMatrix.numberOfColumns = numberOfColumns;
+	stackel -> numericMatrix.data = x;
+}
+static void pushString (char32 *x) {
+	Stackel stackel = & theStack [++ w];
+	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
+	if (w > wmax) wmax ++;
+	stackel -> which = Stackel_STRING;
+	stackel -> string = x;
 }
 static void pushVariable (InterpreterVariable var) {
 	Stackel stackel = & theStack [++ w];
@@ -1940,8 +2006,9 @@ static void pushVariable (InterpreterVariable var) {
 const char32 *Stackel_whichText (Stackel me) {
 	return
 		my which == Stackel_NUMBER ? U"a number" :
+		my which == Stackel_NUMERIC_VECTOR ? U"a numeric vector" :
+		my which == Stackel_NUMERIC_MATRIX ? U"a numeric matrix" :
 		my which == Stackel_STRING ? U"a string" :
-		my which == Stackel_NUMERIC_ARRAY ? U"a numeric array" :
 		my which == Stackel_STRING_ARRAY ? U"a string array" :
 		U"???";
 }
@@ -2293,47 +2360,85 @@ static void do_function_dd_d (double (*f) (double, double)) {
 			Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
 	}
 }
-static void do_function_dd_d_numar (double (*f) (double, double)) {
+static void do_function_dd_d_numvec (double (*f) (double, double)) {
+	Stackel n = pop;
+	Melder_assert (n -> which == Stackel_NUMBER);
+	if (n -> number != 3)
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
+	Stackel y = pop, x = pop, a = pop;
+	if (a->which == Stackel_NUMERIC_VECTOR && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
+		long numberOfElements = a->numericVector.numberOfElements;
+		double *newData = NUMvector <double> (1, numberOfElements);
+		for (long ielem = 1; ielem <= numberOfElements; ielem ++) {
+			newData [ielem] = f (x->number, y->number);
+		}
+		pushNumericVector (numberOfElements, newData);
+	} else {
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
+			U" requires one vector argument and two numeric arguments, not ",
+			Stackel_whichText (a), U", ", Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
+	}
+}
+static void do_function_dd_d_nummat (double (*f) (double, double)) {
 	Stackel n = pop;
 	Melder_assert (n -> which == Stackel_NUMBER);
 	if (n -> number != 3)
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
 	Stackel y = pop, x = pop, a = pop;
-	if (a->which == Stackel_NUMERIC_ARRAY && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		long numberOfRows = a->numericArray.numberOfRows;
-		long numberOfColumns = a->numericArray.numberOfColumns;
+	if (a->which == Stackel_NUMERIC_MATRIX && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
+		long numberOfRows = a->numericMatrix.numberOfRows;
+		long numberOfColumns = a->numericMatrix.numberOfColumns;
 		double **newData = NUMmatrix <double> (1, numberOfRows, 1, numberOfColumns);
 		for (long irow = 1; irow <= numberOfRows; irow ++) {
 			for (long icol = 1; icol <= numberOfColumns; icol ++) {
 				newData [irow] [icol] = f (x->number, y->number);
 			}
 		}
-		pushNumericArray (numberOfRows, numberOfColumns, newData);
+		pushNumericMatrix (numberOfRows, numberOfColumns, newData);
 	} else {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
-			U" requires one array argument and two numeric arguments, not ",
+			U" requires one matrix argument and two numeric arguments, not ",
 			Stackel_whichText (a), U", ", Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
 	}
 }
-static void do_function_ll_l_numar (long (*f) (long, long)) {
+static void do_function_ll_l_numvec (long (*f) (long, long)) {
 	Stackel n = pop;
 	Melder_assert (n -> which == Stackel_NUMBER);
 	if (n -> number != 3)
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
 	Stackel y = pop, x = pop, a = pop;
-	if (a->which == Stackel_NUMERIC_ARRAY && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		long numberOfRows = a->numericArray.numberOfRows;
-		long numberOfColumns = a->numericArray.numberOfColumns;
+	if (a->which == Stackel_NUMERIC_VECTOR && x->which == Stackel_NUMBER) {
+		long numberOfElements = a->numericVector.numberOfElements;
+		double *newData = NUMvector <double> (1, numberOfElements);
+		for (long ielem = 1; ielem <= numberOfElements; ielem ++) {
+			newData [ielem] = f (lround (x->number), lround (y->number));
+		}
+		pushNumericVector (numberOfElements, newData);
+	} else {
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
+			U" requires one vector argument and two numeric arguments, not ",
+			Stackel_whichText (a), U", ", Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
+	}
+}
+static void do_function_ll_l_nummat (long (*f) (long, long)) {
+	Stackel n = pop;
+	Melder_assert (n -> which == Stackel_NUMBER);
+	if (n -> number != 3)
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
+	Stackel y = pop, x = pop, a = pop;
+	if (a->which == Stackel_NUMERIC_MATRIX && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
+		long numberOfRows = a->numericMatrix.numberOfRows;
+		long numberOfColumns = a->numericMatrix.numberOfColumns;
 		double **newData = NUMmatrix <double> (1, numberOfRows, 1, numberOfColumns);
 		for (long irow = 1; irow <= numberOfRows; irow ++) {
 			for (long icol = 1; icol <= numberOfColumns; icol ++) {
 				newData [irow] [icol] = f (lround (x->number), lround (y->number));
 			}
 		}
-		pushNumericArray (numberOfRows, numberOfColumns, newData);
+		pushNumericMatrix (numberOfRows, numberOfColumns, newData);
 	} else {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
-			U" requires one array argument and two numeric arguments, not ",
+			U" requires one matrix argument and two numeric arguments, not ",
 			Stackel_whichText (a), U", ", Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
 	}
 }
@@ -2870,37 +2975,54 @@ static void do_imax () {
 	}
 	pushNumber (result);
 }
-static void do_zeroNumar () {
+static void do_zeroNumvec () {
 	Stackel n = pop;
 	Melder_assert (n -> which == Stackel_NUMBER);
 	int rank = lround (n -> number);
 	if (rank < 1)
-		Melder_throw (U"The function \"zero#\" requires arguments.");
-	long numberOfRows = 1, numberOfColumns = 1;
+		Melder_throw (U"The function \"zero#\" requires an argument.");
 	if (rank > 1) {
-		if (rank > 2)
-			Melder_throw (U"The function \"zero#\" cannot have more than two arguments.");
-		Stackel ncol = pop;
-		if (ncol -> which != Stackel_NUMBER)
-			Melder_throw (U"In the function \"zero#\", the number of columns has to be a number, not ", Stackel_whichText (ncol), U".");
-		numberOfColumns = lround (ncol -> number);
-	}
+		Melder_throw (U"The function \"zero#\" cannot have more than one argument (consider using zero##).");
+	}
+	long numberOfElements;
+	Stackel nelem = pop;
+	if (nelem -> which != Stackel_NUMBER)
+		Melder_throw (U"In the function \"zero#\", the number of elements has to be a number, not ", Stackel_whichText (nelem), U".");
+	numberOfElements = lround (nelem -> number);
+	if (numberOfElements == NUMundefined)
+		Melder_throw (U"In the function \"zero#\", the number of elements is undefined.");
+	if (numberOfElements <= 0)
+		Melder_throw (U"In the function \"zero#\", the number of elements has to be positive.");
+	autoNUMvector <double> data (1, numberOfElements);
+	pushNumericVector (numberOfElements, data.transfer());
+}
+static void do_zeroNummat () {
+	Stackel n = pop;
+	Melder_assert (n -> which == Stackel_NUMBER);
+	int rank = lround (n -> number);
+	if (rank != 2)
+		Melder_throw (U"The function \"zero##\" requires two arguments.");
+	long numberOfRows = 1, numberOfColumns = 1;
+	Stackel ncol = pop;
+	if (ncol -> which != Stackel_NUMBER)
+		Melder_throw (U"In the function \"zero##\", the number of columns has to be a number, not ", Stackel_whichText (ncol), U".");
+	numberOfColumns = lround (ncol -> number);
 	Stackel nrow = pop;
 	if (nrow -> which != Stackel_NUMBER)
-		Melder_throw (U"In the function \"zero#\", the number of rows has to be a number, not ", Stackel_whichText (nrow), U".");
+		Melder_throw (U"In the function \"zero##\", the number of rows has to be a number, not ", Stackel_whichText (nrow), U".");
 	numberOfRows = lround (nrow -> number);
 	if (numberOfRows == NUMundefined)
-		Melder_throw (U"In the function \"zero#\", the number of rows is undefined.");
+		Melder_throw (U"In the function \"zero##\", the number of rows is undefined.");
 	if (numberOfColumns == NUMundefined)
-		Melder_throw (U"In the function \"zero#\", the number of columns is undefined.");
+		Melder_throw (U"In the function \"zero##\", the number of columns is undefined.");
 	if (numberOfRows <= 0)
-		Melder_throw (U"In the function \"zero#\", the number of rows has to be positive.");
+		Melder_throw (U"In the function \"zero##\", the number of rows has to be positive.");
 	if (numberOfColumns <= 0)
-		Melder_throw (U"In the function \"zero#\", the number of columns has to be positive.");
+		Melder_throw (U"In the function \"zero##\", the number of columns has to be positive.");
 	autoNUMmatrix <double> data (1, numberOfRows, 1, numberOfColumns);
-	pushNumericArray (numberOfRows, numberOfColumns, data.transfer());
+	pushNumericMatrix (numberOfRows, numberOfColumns, data.transfer());
 }
-static void do_linearNumar () {
+static void do_linearNumvec () {
 	Stackel stackel_narg = pop;
 	Melder_assert (stackel_narg -> which == Stackel_NUMBER);
 	int narg = lround (stackel_narg -> number);
@@ -2933,14 +3055,14 @@ static void do_linearNumar () {
 	long numberOfSteps = lround (stack_numberOfSteps -> number);
 	if (numberOfSteps <= 0)
 		Melder_throw (U"In the function \"linear#\", the number of steps (third argument) has to be positive, not ", numberOfSteps, U".");
-	autoNUMmatrix <double> data (1, numberOfSteps, 1, 1);
-	for (long irow = 1; irow <= numberOfSteps; irow ++) {
-		data [irow] [1] = excludeEdges ?
-			minimum + (irow - 0.5) * (maximum - minimum) / numberOfSteps :
-			minimum + (irow - 1) * (maximum - minimum) / (numberOfSteps - 1);
+	autoNUMvector <double> data (1, numberOfSteps);
+	for (long ielem = 1; ielem <= numberOfSteps; ielem ++) {
+		data [ielem] = excludeEdges ?
+			minimum + (ielem - 0.5) * (maximum - minimum) / numberOfSteps :
+			minimum + (ielem - 1) * (maximum - minimum) / (numberOfSteps - 1);
 	}
-	if (! excludeEdges) data [numberOfSteps] [1] = maximum;   // remove rounding problems
-	pushNumericArray (numberOfSteps, 1, data.transfer());
+	if (! excludeEdges) data [numberOfSteps] = maximum;   // remove rounding problems
+	pushNumericVector (numberOfSteps, data.transfer());
 }
 static void do_numberOfRows () {
 	Stackel n = pop;
@@ -2948,11 +3070,11 @@ static void do_numberOfRows () {
 	if (n->number != 1)
 		Melder_throw (U"The function \"numberOfRows\" requires one argument.");
 	Stackel array = pop;
-	if (array->which == Stackel_NUMERIC_ARRAY) {
-		pushNumber (array->numericArray.numberOfRows);
+	if (array->which == Stackel_NUMERIC_MATRIX) {
+		pushNumber (array->numericMatrix.numberOfRows);
 	} else {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
-			U" requires a numeric argument, not ", Stackel_whichText (array), U".");
+			U" requires a matrix argument, not ", Stackel_whichText (array), U".");
 	}
 }
 static void do_numberOfColumns () {
@@ -2961,11 +3083,11 @@ static void do_numberOfColumns () {
 	if (n->number != 1)
 		Melder_throw (U"The function \"numberOfColumns\" requires one argument.");
 	Stackel array = pop;
-	if (array->which == Stackel_NUMERIC_ARRAY) {
-		pushNumber (array->numericArray.numberOfColumns);
+	if (array->which == Stackel_NUMERIC_MATRIX) {
+		pushNumber (array->numericMatrix.numberOfColumns);
 	} else {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
-			U" requires a numeric argument, not ", Stackel_whichText (array), U".");
+			U" requires a matrix argument, not ", Stackel_whichText (array), U".");
 	}
 }
 static void do_editor () {
@@ -3008,37 +3130,45 @@ static void do_hash () {
 	}
 }
 
-static void do_numericArrayElement () {
-	Stackel n = pop;
-	Melder_assert (n -> which == Stackel_NUMBER);
-	int narg = lround (n -> number);
-	if (narg < 1 || narg > 2)
-		Melder_throw (U"Array indexing requires one or two arguments.");
-	InterpreterVariable array = parse [programPointer]. content.variable;
+static void do_numericVectorElement () {
+	InterpreterVariable vector = parse [programPointer]. content.variable;
+	long element = 1;   // default
+	Stackel r = pop;
+	if (r -> which != Stackel_NUMBER)
+		Melder_throw (U"In vector indexing, the index has to be a number, not ", Stackel_whichText (r), U".");
+	if (r -> number == NUMundefined)
+		Melder_throw (U"The element index is undefined.");
+	element = lround (r -> number);
+	if (element <= 0)
+		Melder_throw (U"In vector indexing, the element index has to be positive.");
+	if (element > vector -> numericVectorValue. numberOfElements)
+		Melder_throw (U"Element index out of bounds.");
+	pushNumber (vector -> numericVectorValue. data [element]);
+}
+static void do_numericMatrixElement () {
+	InterpreterVariable matrix = parse [programPointer]. content.variable;
 	long row = 1, column = 1;   // default
-	if (narg > 1) {
-		Stackel c = pop;
-		if (c -> which != Stackel_NUMBER)
-			Melder_throw (U"In array indexing, the column index has to be a number, not ", Stackel_whichText (c), U".");
-		if (c -> number == NUMundefined)
-			Melder_throw (U"The column index is undefined.");
-		column = lround (c -> number);
-		if (column <= 0)
-			Melder_throw (U"In array indexing, the column index has to be positive.");
-		if (column > array -> numericArrayValue. numberOfColumns)
-			Melder_throw (U"Column index out of bounds.");
-	}
+	Stackel c = pop;
+	if (c -> which != Stackel_NUMBER)
+		Melder_throw (U"In matrix indexing, the column index has to be a number, not ", Stackel_whichText (c), U".");
+	if (c -> number == NUMundefined)
+		Melder_throw (U"The column index is undefined.");
+	column = lround (c -> number);
+	if (column <= 0)
+		Melder_throw (U"In matrix indexing, the column index has to be positive.");
+	if (column > matrix -> numericMatrixValue. numberOfColumns)
+		Melder_throw (U"Column index out of bounds.");
 	Stackel r = pop;
 	if (r -> which != Stackel_NUMBER)
-		Melder_throw (U"In array indexing, the row index has to be a number, not ", Stackel_whichText (r), U".");
+		Melder_throw (U"In matrix indexing, the row index has to be a number, not ", Stackel_whichText (r), U".");
 	if (r -> number == NUMundefined)
 		Melder_throw (U"The row index is undefined.");
 	row = lround (r -> number);
 	if (row <= 0)
-		Melder_throw (U"In array indexing, the row index has to be positive.");
-	if (row > array -> numericArrayValue. numberOfRows)
+		Melder_throw (U"In matrix indexing, the row index has to be positive.");
+	if (row > matrix -> numericMatrixValue. numberOfRows)
 		Melder_throw (U"Row index out of bounds.");
-	pushNumber (array -> numericArrayValue. data [row] [column]);
+	pushNumber (matrix -> numericMatrixValue. data [row] [column]);
 }
 static void do_indexedNumericVariable () {
 	Stackel n = pop;
@@ -4843,11 +4973,15 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case MAX_: { do_max ();
 } break; case IMIN_: { do_imin ();
 } break; case IMAX_: { do_imax ();
-} break; case ZERO_NUMAR_: { do_zeroNumar ();
-} break; case LINEAR_NUMAR_: { do_linearNumar ();
-} break; case RANDOM_UNIFORM_NUMAR_: { do_function_dd_d_numar (NUMrandomUniform);
-} break; case RANDOM_INTEGER_NUMAR_: { do_function_ll_l_numar (NUMrandomInteger);
-} break; case RANDOM_GAUSS_NUMAR_: { do_function_dd_d_numar (NUMrandomGauss);
+} break; case ZERO_NUMVEC_: { do_zeroNumvec ();
+} break; case ZERO_NUMMAT_: { do_zeroNummat ();
+} break; case LINEAR_NUMVEC_: { do_linearNumvec ();
+} break; case RANDOM_UNIFORM_NUMVEC_: { do_function_dd_d_numvec (NUMrandomUniform);
+} break; case RANDOM_UNIFORM_NUMMAT_: { do_function_dd_d_nummat (NUMrandomUniform);
+} break; case RANDOM_INTEGER_NUMVEC_: { do_function_ll_l_numvec (NUMrandomInteger);
+} break; case RANDOM_INTEGER_NUMMAT_: { do_function_ll_l_nummat (NUMrandomInteger);
+} break; case RANDOM_GAUSS_NUMVEC_: { do_function_dd_d_numvec (NUMrandomGauss);
+} break; case RANDOM_GAUSS_NUMMAT_: { do_function_dd_d_nummat (NUMrandomGauss);
 } break; case NUMBER_OF_ROWS_: { do_numberOfRows ();
 } break; case NUMBER_OF_COLUMNS_: { do_numberOfColumns ();
 } break; case EDITOR_: { do_editor ();
@@ -4981,7 +5115,8 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case POP_2_: {
 	w -= 2;
 	//Melder_casual (U"total ", theStack[w].number);
-} break; case NUMERIC_ARRAY_ELEMENT_: { do_numericArrayElement ();
+} break; case NUMERIC_VECTOR_ELEMENT_: { do_numericVectorElement ();
+} break; case NUMERIC_MATRIX_ELEMENT_: { do_numericMatrixElement ();
 } break; case INDEXED_NUMERIC_VARIABLE_: { do_indexedNumericVariable ();
 } break; case INDEXED_STRING_VARIABLE_: { do_indexedStringVariable ();
 } break; case VARIABLE_REFERENCE_: {
@@ -5020,15 +5155,20 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case NUMERIC_VARIABLE_: {
 	InterpreterVariable var = f [programPointer]. content.variable;
 	pushNumber (var -> numericValue);
+} break; case NUMERIC_VECTOR_VARIABLE_: {
+	InterpreterVariable var = f [programPointer]. content.variable;
+	double *data = NUMvector_copy (var -> numericVectorValue. data,
+		1, var -> numericVectorValue. numberOfElements);
+	pushNumericVector (var -> numericVectorValue. numberOfElements, data);
+} break; case NUMERIC_MATRIX_VARIABLE_: {
+	InterpreterVariable var = f [programPointer]. content.variable;
+	double **data = NUMmatrix_copy (var -> numericMatrixValue. data,
+		1, var -> numericMatrixValue. numberOfRows, 1, var -> numericMatrixValue. numberOfColumns);
+	pushNumericMatrix (var -> numericMatrixValue. numberOfRows, var -> numericMatrixValue. numberOfColumns, data);
 } break; case STRING_VARIABLE_: {
 	InterpreterVariable var = f [programPointer]. content.variable;
 	autostring32 string = Melder_dup (var -> stringValue);
 	pushString (string.transfer());
-} break; case NUMERIC_ARRAY_VARIABLE_: {
-	InterpreterVariable var = f [programPointer]. content.variable;
-	double **data = NUMmatrix_copy (var -> numericArrayValue. data,
-		1, var -> numericArrayValue. numberOfRows, 1, var -> numericArrayValue. numberOfColumns);
-	pushNumericArray (var -> numericArrayValue. numberOfRows, var -> numericArrayValue. numberOfColumns, data);
 } break; default: Melder_throw (U"Symbol \"", Formula_instructionNames [parse [programPointer]. symbol], U"\" without action.");
 			} // endswitch
 			programPointer ++;
@@ -5036,22 +5176,32 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 		if (w != 1) Melder_fatal (U"Formula: stackpointer ends at ", w, U" instead of 1.");
 		if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_NUMERIC) {
 			if (theStack [1]. which == Stackel_STRING) Melder_throw (U"Found a string expression instead of a numeric expression.");
-			if (theStack [1]. which == Stackel_NUMERIC_ARRAY) Melder_throw (U"Found a numeric array expression instead of a numeric expression.");
+			if (theStack [1]. which == Stackel_NUMERIC_VECTOR) Melder_throw (U"Found a vector expression instead of a numeric expression.");
+			if (theStack [1]. which == Stackel_NUMERIC_MATRIX) Melder_throw (U"Found a matrix expression instead of a numeric expression.");
 			result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC;
 			result -> result.numericResult = theStack [1]. number;
 		} else if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_STRING) {
 			if (theStack [1]. which == Stackel_NUMBER)
 				Melder_throw (U"Found a numeric expression (value ", theStack [1]. number, U") instead of a string expression.");
-			if (theStack [1]. which == Stackel_NUMERIC_ARRAY) Melder_throw (U"Found a numeric array expression instead of a string expression.");
+			if (theStack [1]. which == Stackel_NUMERIC_VECTOR) Melder_throw (U"Found a vector expression instead of a string expression.");
+			if (theStack [1]. which == Stackel_NUMERIC_MATRIX) Melder_throw (U"Found a matrix expression instead of a string expression.");
 			result -> expressionType = kFormula_EXPRESSION_TYPE_STRING;
 			result -> result.stringResult = theStack [1]. string;   // dangle...
 			theStack [1]. string = nullptr;   // ...undangle (and disown)
-		} else if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY) {
-			if (theStack [1]. which == Stackel_NUMBER) Melder_throw (U"Found a numeric expression instead of a numeric array expression.");
-			if (theStack [1]. which == Stackel_STRING) Melder_throw (U"Found a string expression instead of a numeric array expression.");
-			result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY;
-			result -> result.numericArrayResult = theStack [1]. numericArray;   // dangle
-			theStack [1]. numericArray = theZeroNumericArray;   // ...undangle (and disown)
+		} else if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR) {
+			if (theStack [1]. which == Stackel_NUMBER) Melder_throw (U"Found a numeric expression instead of a vector expression.");
+			if (theStack [1]. which == Stackel_STRING) Melder_throw (U"Found a string expression instead of a vector expression.");
+			if (theStack [1]. which == Stackel_NUMERIC_MATRIX) Melder_throw (U"Found a matrix expression instead of a vector expression.");
+			result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR;
+			result -> result.numericVectorResult = theStack [1]. numericVector;   // dangle
+			theStack [1]. numericVector = theZeroNumericVector;   // ...undangle (and disown)
+		} else if (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX) {
+			if (theStack [1]. which == Stackel_NUMBER) Melder_throw (U"Found a numeric expression instead of a matrix expression.");
+			if (theStack [1]. which == Stackel_STRING) Melder_throw (U"Found a string expression instead of a matrix expression.");
+			if (theStack [1]. which == Stackel_NUMERIC_VECTOR) Melder_throw (U"Found a vector expression instead of a matrix expression.");
+			result -> expressionType = kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX;
+			result -> result.numericMatrixResult = theStack [1]. numericMatrix;   // dangle
+			theStack [1]. numericMatrix = theZeroNumericMatrix;   // ...undangle (and disown)
 		} else {
 			Melder_assert (theExpressionType [theLevel] == kFormula_EXPRESSION_TYPE_UNKNOWN);
 			if (theStack [1]. which == Stackel_NUMBER) {
diff --git a/sys/Formula.h b/sys/Formula.h
index 5973bb2..a3e688b 100644
--- a/sys/Formula.h
+++ b/sys/Formula.h
@@ -2,7 +2,7 @@
 #define _Formula_h_
 /* Formula.h
  *
- * Copyright (C) 1990-2011,2013,2014,2015 Paul Boersma
+ * Copyright (C) 1990-2011,2013,2014,2015,2016 Paul Boersma
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,11 +22,19 @@
 
 #define kFormula_EXPRESSION_TYPE_NUMERIC  0
 #define kFormula_EXPRESSION_TYPE_STRING  1
-#define kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY  2
-#define kFormula_EXPRESSION_TYPE_STRING_ARRAY  3
-#define kFormula_EXPRESSION_TYPE_UNKNOWN  4
+#define kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR  2
+#define kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX  3
+#define kFormula_EXPRESSION_TYPE_NUMERIC_TENSOR3  4
+#define kFormula_EXPRESSION_TYPE_NUMERIC_TENSOR4  5
+#define kFormula_EXPRESSION_TYPE_STRING_ARRAY  6
+#define kFormula_EXPRESSION_TYPE_UNKNOWN  7
 
-struct Formula_NumericArray {
+struct Formula_NumericVector {
+	long numberOfElements;
+	double *data;
+};
+
+struct Formula_NumericMatrix {
 	long numberOfRows, numberOfColumns;
 	double **data;
 };
@@ -36,14 +44,18 @@ Thing_declare (InterpreterVariable);
 typedef struct structStackel {
 	#define Stackel_NUMBER  0
 	#define Stackel_STRING  1
-	#define Stackel_NUMERIC_ARRAY  2
-	#define Stackel_STRING_ARRAY  3
+	#define Stackel_NUMERIC_VECTOR  2
+	#define Stackel_NUMERIC_MATRIX  3
+	#define Stackel_NUMERIC_TENSOR3  4
+	#define Stackel_NUMERIC_TENSOR4  5
+	#define Stackel_STRING_ARRAY  6
 	#define Stackel_VARIABLE  -1
 	int which;   /* 0 or negative = no clean-up required, positive = requires clean-up */
 	union {
 		double number;
 		char32 *string;
-		struct Formula_NumericArray numericArray;
+		struct Formula_NumericVector numericVector;
+		struct Formula_NumericMatrix numericMatrix;
 		InterpreterVariable variable;
 	};
 } *Stackel;
@@ -54,7 +66,8 @@ struct Formula_Result {
 	union {
 		double numericResult;
 		char32 *stringResult;
-		struct Formula_NumericArray numericArrayResult;
+		struct Formula_NumericVector numericVectorResult;
+		struct Formula_NumericMatrix numericMatrixResult;
 	} result;
 };
 
diff --git a/sys/Graphics_colour.cpp b/sys/Graphics_colour.cpp
index 24f52fc..5a0ad57 100644
--- a/sys/Graphics_colour.cpp
+++ b/sys/Graphics_colour.cpp
@@ -168,7 +168,7 @@ static void highlight (Graphics graphics, long x1DC, long x2DC, long y1DC, long
 			if (width <= 0 || height <= 0) return;
 			GuiCocoaDrawingArea *drawingArea = (GuiCocoaDrawingArea *) my d_drawingArea -> d_widget;
 			if (drawingArea) {
-				bool cacheImageInRectWillWork = ( Melder_systemVersion < 101100 || Melder_systemVersion > 101104 );
+				bool cacheImageInRectWillWork = ( Melder_systemVersion < 101100 || Melder_systemVersion > 101105 );
 				if (cacheImageInRectWillWork) {
 					NSView *nsView = my d_macView;
 					if (direction == 1) {   // forward
@@ -280,7 +280,7 @@ static void highlight2 (Graphics graphics, long x1DC, long x2DC, long y1DC, long
 		#elif cocoa
 			GuiCocoaDrawingArea *drawingArea = (GuiCocoaDrawingArea *) my d_drawingArea -> d_widget;
 			if (drawingArea) {
-				bool cacheImageInRectWillWork = ( Melder_systemVersion < 101100 || Melder_systemVersion > 101104 );
+				bool cacheImageInRectWillWork = ( Melder_systemVersion < 101100 || Melder_systemVersion > 101105 );
 				if (cacheImageInRectWillWork) {
 					NSView *nsView = my d_macView;
 					if (direction == 1) {
diff --git a/sys/Interpreter.cpp b/sys/Interpreter.cpp
index c681c4d..fd05c8a 100644
--- a/sys/Interpreter.cpp
+++ b/sys/Interpreter.cpp
@@ -1,6 +1,6 @@
 /* Interpreter.cpp
  *
- * Copyright (C) 1993-2011,2013,2014,2015 Paul Boersma
+ * Copyright (C) 1993-2011,2013,2014,2015,2016 Paul Boersma
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -42,9 +42,10 @@ extern structMelderDir praatDir;
 Thing_implement (InterpreterVariable, SimpleString, 0);
 
 void structInterpreterVariable :: v_destroy () noexcept {
-	Melder_free (string);
-	Melder_free (stringValue);
-	NUMmatrix_free (numericArrayValue. data, 1, 1);
+	Melder_free (our string);
+	Melder_free (our stringValue);
+	NUMvector_free (our numericVectorValue. data, 1);
+	NUMmatrix_free (our numericMatrixValue. data, 1, 1);
 	InterpreterVariable_Parent :: v_destroy ();
 }
 
@@ -71,25 +72,21 @@ void structInterpreter :: v_destroy () noexcept {
 	Melder_free (our environmentName);
 	for (int ipar = 1; ipar <= Interpreter_MAXNUM_PARAMETERS; ipar ++)
 		Melder_free (our arguments [ipar]);
-	#if USE_HASH
-	if (our variablesMap) {
-		for (auto it = our variablesMap -> begin(); it != our variablesMap -> end(); it ++) {
+	//if (our variablesMap) {
+		for (auto it = our variablesMap. begin(); it != our variablesMap. end(); it ++) {
 			InterpreterVariable var = it -> second;
 			forget (var);
 		}
-		delete (our variablesMap);
-	}
-	#endif
+	//	delete (our variablesMap);
+	//}
 	Interpreter_Parent :: v_destroy ();
 }
 
 autoInterpreter Interpreter_create (char32 *environmentName, ClassInfo editorClass) {
 	try {
 		autoInterpreter me = Thing_new (Interpreter);
-		#if USE_HASH
-		my variablesMap = new std::unordered_map <std::u32string, InterpreterVariable>;
-		my variablesMap -> max_load_factor (0.65f);
-		#endif
+		//my variablesMap = new std::unordered_map <std::u32string, InterpreterVariable>;
+		my variablesMap. max_load_factor (0.65f);
 		my environmentName = Melder_dup (environmentName);
 		my editorClass = editorClass;
 		return me;
@@ -170,6 +167,10 @@ void Melder_includeIncludeFiles (char32 **text) {
 	}
 }
 
+inline static bool Melder_isblank (char32 kar) {
+	return kar == U' ' || kar == U'\t';
+}
+
 long Interpreter_readParameters (Interpreter me, char32 *text) {
 	char32 *formLocation = nullptr;
 	long npar = 0;
@@ -180,7 +181,7 @@ long Interpreter_readParameters (Interpreter me, char32 *text) {
 	{// scope
 		char32 *p = text;
 		for (;;) {
-			while (*p == ' ' || *p == '\t') p ++;
+			while (Melder_isblank (*p)) p ++;
 			if (str32nequ (p, U"form ", 5)) {
 				formLocation = p;
 				break;
@@ -204,13 +205,13 @@ long Interpreter_readParameters (Interpreter me, char32 *text) {
 		while (newLine) {
 			char32 *line = newLine + 1, *p;
 			int type = 0;
-			while (*line == U' ' || *line == U'\t') line ++;
+			while (Melder_isblank (*line)) line ++;
 			while (*line == U'#' || *line == U';' || *line == U'!' || *line == U'\n') {
 				newLine = str32chr (line, U'\n');
 				if (! newLine)
 					Melder_throw (U"Unfinished form.");
 				line = newLine + 1;
-				while (*line == U' ' || *line == U'\t') line ++;
+				while (Melder_isblank (*line)) line ++;
 			}
 			if (str32nequ (line, U"endform", 7)) break;
 			if (str32nequ (line, U"word ", 5)) { type = Interpreter_WORD; p = line + 5; }
@@ -255,7 +256,7 @@ long Interpreter_readParameters (Interpreter me, char32 *text) {
 				my arguments [5] := "Blue"
 			*/
 			if (type <= Interpreter_OPTIONMENU) {
-				while (*p == U' ' || *p == U'\t') p ++;
+				while (Melder_isblank (*p)) p ++;
 				if (*p == U'\n' || *p == U'\0')
 					Melder_throw (U"Missing parameter:\n\"", line, U"\".");
 				char32 *q = my parameters [++ my numberOfParameters];
@@ -265,7 +266,7 @@ long Interpreter_readParameters (Interpreter me, char32 *text) {
 			} else {
 				my parameters [++ my numberOfParameters] [0] = U'\0';
 			}
-			while (*p == U' ' || *p == U'\t') p ++;
+			while (Melder_isblank (*p)) p ++;
 			newLine = str32chr (p, U'\n');
 			if (newLine) *newLine = U'\0';
 			Melder_free (my arguments [my numberOfParameters]);
@@ -462,7 +463,7 @@ void Interpreter_getArgumentsFromString (Interpreter me, const char32 *arguments
 	 * Leading spaces are skipped, but trailing spaces are included.
 	 */
 	if (size > 0) {
-		while (*arguments == U' ' || *arguments == U'\t') arguments ++;
+		while (Melder_isblank (*arguments)) arguments ++;
 		Melder_free (my arguments [size]);
 		my arguments [size] = Melder_dup_f (arguments);
 	}
@@ -603,53 +604,35 @@ void Interpreter_getArgumentsFromArgs (Interpreter me, int narg, Stackel args) {
 }
 
 static void Interpreter_addNumericVariable (Interpreter me, const char32 *key, double value) {
-	#if USE_HASH
 	autoInterpreterVariable variable = InterpreterVariable_create (key);
 	variable -> numericValue = value;
-	(*my variablesMap) [key] = variable.get();   // YUCK
+	my variablesMap [key] = variable.get();   // YUCK
 	variable.releaseToAmbiguousOwner();
-	#else
-	autoInterpreterVariable variable = InterpreterVariable_create (key);
-	variable -> numericValue = value;
-	my variables. addItem_move (variable.move());
-	#endif
 }
 
 static void Interpreter_addStringVariable (Interpreter me, const char32 *key, const char32 *value) {
-	#if USE_HASH
 	autoInterpreterVariable variable = InterpreterVariable_create (key);
 	variable -> stringValue = Melder_dup (value);
-	(*my variablesMap) [key] = variable.get();   // YUCK
+	my variablesMap [key] = variable.get();   // YUCK
 	variable.releaseToAmbiguousOwner();
-	#else
-	autoInterpreterVariable variable = InterpreterVariable_create (key);
-	variable -> stringValue = Melder_dup (value);
-	my variables. addItem_move (variable.move());
-	#endif
 }
 
 InterpreterVariable Interpreter_hasVariable (Interpreter me, const char32 *key) {
 	Melder_assert (key);
-	#if USE_HASH
-	auto it = my variablesMap -> find (key [0] == U'.' ? Melder_cat (my procedureNames [my callDepth], key) : key);
-	if (it != my variablesMap -> end()) {
+	auto it = my variablesMap. find (key [0] == U'.' ? Melder_cat (my procedureNames [my callDepth], key) : key);
+	if (it != my variablesMap. end()) {
 		return it -> second;
 	} else {
 		return nullptr;
 	}
-	#else
-	long variableNumber = my variables. lookUp (key [0] == U'.' ? Melder_cat (my procedureNames [my callDepth], key) : key);
-	return variableNumber ? my variables.at [variableNumber] : nullptr;
-	#endif
 }
 
 InterpreterVariable Interpreter_lookUpVariable (Interpreter me, const char32 *key) {
 	Melder_assert (key);
 	const char32 *variableNameIncludingProcedureName =
 		key [0] == U'.' ? Melder_cat (my procedureNames [my callDepth], key) : key;
-	#if USE_HASH
-	auto it = my variablesMap -> find (variableNameIncludingProcedureName);
-	if (it != my variablesMap -> end()) {
+	auto it = my variablesMap. find (variableNameIncludingProcedureName);
+	if (it != my variablesMap. end()) {
 		return it -> second;
 	}
 	/*
@@ -658,17 +641,8 @@ InterpreterVariable Interpreter_lookUpVariable (Interpreter me, const char32 *ke
 	autoInterpreterVariable variable = InterpreterVariable_create (variableNameIncludingProcedureName);
 	InterpreterVariable variable_ref = variable.get();
 	variable.releaseToAmbiguousOwner();   // YUCK
-	(*my variablesMap) [variableNameIncludingProcedureName] = variable_ref;
+	my variablesMap [variableNameIncludingProcedureName] = variable_ref;
 	return variable_ref;
-	#else
-	long variableNumber = my variables. lookUp (variableNameIncludingProcedureName);
-	if (variableNumber) return my variables.at [variableNumber];   // already exists
-	/*
-	 * The variable doesn't yet exist: create a new one.
-	 */
-	autoInterpreterVariable variable = InterpreterVariable_create (variableNameIncludingProcedureName);
-	return my variables. addItem_move (variable.move());
-	#endif
 }
 
 static long lookupLabel (Interpreter me, const char32 *labelName) {
@@ -759,7 +733,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 		lines.reset (1, numberOfLines);
 		for (lineNumber = 1, command = text; lineNumber <= numberOfLines; lineNumber ++, command += str32len (command) + 1 + chopped) {
 			int length;
-			while (*command == U' ' || *command == U'\t' || *command == UNICODE_NO_BREAK_SPACE) command ++;   // nbsp can occur for scripts copied from the manual
+			while (Melder_isblank (*command) || *command == UNICODE_NO_BREAK_SPACE) command ++;   // nbsp can occur for scripts copied from the manual
 			length = str32len (command);
 			/*
 			 * Chop trailing spaces?
@@ -796,13 +770,11 @@ void Interpreter_run (Interpreter me, char32 *text) {
 		/*
 		 * Copy the parameter names and argument values into the array of variables.
 		 */
-		#if USE_HASH
-		for (auto it = my variablesMap -> begin(); it != my variablesMap -> end(); it ++) {
+		for (auto it = my variablesMap. begin(); it != my variablesMap. end(); it ++) {
 			InterpreterVariable var = it -> second;
 			forget (var);
 		}
-		my variablesMap -> clear ();
-		#endif
+		my variablesMap. clear ();
 		for (ipar = 1; ipar <= my numberOfParameters; ipar ++) {
 			char32 parameter [200];
 			/*
@@ -938,7 +910,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 						 * Look for a function name.
 						 */
 						char32 *p = command2.string + 1;
-						while (*p == U' ' || *p == U'\t') p ++;   // skip whitespace
+						while (Melder_isblank (*p)) p ++;   // skip whitespace
 						char32 *callName = p;
 						while (*p != U'\0' && *p != U' ' && *p != U'\t' && *p != U'(' && *p != U':') p ++;
 						if (p == callName) Melder_throw (U"Missing procedure name after \"@\".");
@@ -948,7 +920,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 							*p = U'\0';   // close procedure name
 							if (! parenthesisOrColonFound) {
 								p ++;   // step over first white space
-								while (*p != U'\0' && (*p == U' ' || *p == U'\t')) p ++;   // skip more whitespace
+								while (Melder_isblank (*p)) p ++;   // skip more whitespace
 								hasArguments = ( *p != U'\0' );
 								parenthesisOrColonFound = ( *p == U'(' || *p == U':' );
 								if (hasArguments && ! parenthesisOrColonFound)
@@ -964,9 +936,9 @@ void Interpreter_run (Interpreter me, char32 *text) {
 								linei [4] != U'e' || linei [5] != U'd' || linei [6] != U'u' || linei [7] != U'r' ||
 								linei [8] != U'e' || linei [9] != U' ') continue;
 							q = lines [iline] + 10;
-							while (*q == U' ' || *q == U'\t') q ++;   // skip whitespace before procedure name
+							while (Melder_isblank (*q)) q ++;   // skip whitespace before procedure name
 							char32 *procName = q;
-							while (*q != U'\0' && *q != U' ' && *q != U'\t' && *q != U'(' && *q != U':') q ++;
+							while (*q != U'\0' && ! Melder_isblank (*q) && *q != U'(' && *q != U':') q ++;
 							if (q == procName) Melder_throw (U"Missing procedure name after 'procedure'.");
 							if (q - procName == callLength && str32nequ (procName, callName, callLength)) {
 								/*
@@ -978,16 +950,16 @@ void Interpreter_run (Interpreter me, char32 *text) {
 								bool parenthesisOrColonFound = ( *q == U'(' || *q == U':' );
 								if (*q) q ++;   // step over parenthesis or colon or first white space
 								if (! parenthesisOrColonFound) {
-									while (*q == U' ' || *q == U'\t') q ++;   // skip more whitespace
+									while (Melder_isblank (*q)) q ++;   // skip more whitespace
 									if (*q == U'(' || *q == U':') q ++;   // step over parenthesis or colon
 								}
 								while (*q && *q != U')') {
 									static MelderString argument { 0 };
 									MelderString_empty (& argument);
-									while (*p == U' ' || *p == U'\t') p ++;
-									while (*q == U' ' || *q == U'\t') q ++;
+									while (Melder_isblank (*p)) p ++;
+									while (Melder_isblank (*q)) q ++;
 									char32 *parameterName = q;
-									while (*q != U'\0' && *q != U' ' && *q != U'\t' && *q != U',' && *q != U')') q ++;   // collect parameter name
+									while (*q != U'\0' && ! Melder_isblank (*q) && *q != U',' && *q != U')') q ++;   // collect parameter name
 									int expressionDepth = 0;
 									for (; *p; p ++) {
 										if (*p == U',') {
@@ -1080,7 +1052,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 							long iline;
 							bool hasArguments;
 							int64 callLength;
-							while (*p == U' ' || *p == U'\t') p ++;   // skip whitespace
+							while (Melder_isblank (*p)) p ++;   // skip whitespace
 							callName = p;
 							while (*p != U'\0' && *p != U' ' && *p != U'\t' && *p != U'(' && *p != U':') p ++;
 							if (p == callName) Melder_throw (U"Missing procedure name after 'call'.");
@@ -1094,7 +1066,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 									linei [4] != U'e' || linei [5] != U'd' || linei [6] != U'u' || linei [7] != U'r' ||
 									linei [8] != U'e' || linei [9] != U' ') continue;
 								q = lines [iline] + 10;
-								while (*q == U' ' || *q == U'\t') q ++;
+								while (Melder_isblank (*q)) q ++;
 								procName = q;
 								while (*q != U'\0' && *q != U' ' && *q != U'\t' && *q != U'(' && *q != U':') q ++;
 								if (q == procName) Melder_throw (U"Missing procedure name after 'procedure'.");
@@ -1111,7 +1083,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 										bool parenthesisOrColonFound = ( *q == U'(' || *q == U':' );
 										q ++;   // step over parenthesis or colon or first white space
 										if (! parenthesisOrColonFound) {
-											while (*q == U' ' || *q == U'\t') q ++;   // skip more whitespace
+											while (Melder_isblank (*q)) q ++;   // skip more whitespace
 											if (*q == U'(' || *q == U':') q ++;   // step over parenthesis or colon
 										}
 										++ p;   // first argument
@@ -1119,7 +1091,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 											char32 *par, save;
 											static MelderString arg { 0 };
 											MelderString_empty (& arg);
-											while (*p == U' ' || *p == U'\t') p ++;
+											while (Melder_isblank (*p)) p ++;
 											while (*q == U' ' || *q == U'\t' || *q == U',' || *q == U')') q ++;
 											par = q;
 											while (*q != U'\0' && *q != U' ' && *q != U'\t' && *q != U',' && *q != U')') q ++;   // collect parameter name
@@ -1496,7 +1468,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 						char32 *endOfVariable = ++ p;
 						char32 *variableName = command2.string;
 						int withFile;
-						while (*p == U' ' || *p == U'\t') p ++;   // go to first token after variable name
+						while (Melder_isblank (*p)) p ++;   // go to first token after variable name
 						if (*p == U'[') {
 							/*
 							 * This must be an assignment to an indexed string variable.
@@ -1509,10 +1481,15 @@ void Interpreter_run (Interpreter me, char32 *text) {
 								static MelderString index { 0 };
 								MelderString_empty (& index);
 								int depth = 0;
-								while ((depth > 0 || (*p != U',' && *p != U']')) && *p != U'\n' && *p != U'\0') {
+								bool inString = false;
+								while ((depth > 0 || (*p != U',' && *p != U']') || inString) && *p != U'\n' && *p != U'\0') {
 									MelderString_appendCharacter (& index, *p);
-									if (*p == U'[') depth ++;
-									else if (*p == U']') depth --;
+									if (*p == U'[') {
+										if (! inString) depth ++;
+									} else if (*p == U']') {
+										if (! inString) depth --;
+									}
+									if (*p == U'"') inString = ! inString;
 									p ++;
 								}
 								if (*p == U'\n' || *p == U'\0')
@@ -1534,7 +1511,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 							variableName = indexedVariableName.string;
 							p ++;   // skip closing bracket
 						}
-						while (*p == U' ' || *p == U'\t') p ++;   // go to first token after (perhaps indexed) variable name
+						while (Melder_isblank (*p)) p ++;   // go to first token after (perhaps indexed) variable name
 						if (*p == U'=') {
 							withFile = 0;   // assignment
 						} else if (*p == U'<') {
@@ -1543,11 +1520,11 @@ void Interpreter_run (Interpreter me, char32 *text) {
 							if (p [1] == U'>')
 								withFile = 2, p ++;   // append to file
 							else
-								withFile = 3;   /* Save to file. */
+								withFile = 3;   // save to file
 						} else Melder_throw (U"Missing '=', '<', or '>' after variable ", variableName, U".");
 						*endOfVariable = U'\0';
 						p ++;
-						while (*p == U' ' || *p == U'\t') p ++;   // go to first token after assignment or I/O symbol
+						while (Melder_isblank (*p)) p ++;   // go to first token after assignment or I/O symbol
 						if (*p == U'\0') {
 							if (withFile != 0)
 								Melder_throw (U"Missing file name after variable ", variableName, U".");
@@ -1601,25 +1578,191 @@ void Interpreter_run (Interpreter me, char32 *text) {
 							var -> stringValue = stringValue;   // var becomes owner
 						}
 					} else if (*p == U'#') {
-						/*
-						 * Assign to a numeric array variable.
-						 */
-						char32 *endOfVariable = ++ p;
-						while (*p == U' ' || *p == U'\t') p ++;   // go to first token after variable name
-						if (*p == U'=') {
-							;
-						} else Melder_throw (U"Missing '=' after variable ", command2.string, U".");
-						*endOfVariable = U'\0';
-						p ++;
-						while (*p == U' ' || *p == U'\t') p ++;   // go to first token after assignment or I/O symbol
-						if (*p == U'\0') {
-							Melder_throw (U"Missing expression after variable ", command2.string, U".");
+						if (p [1] == U'#') {
+							/*
+								Assign to a numeric matrix variable or to a matrix element.
+							*/
+							static MelderString matrixName { 0 };
+							p ++;   // go to second '#'
+							*p = U'\0';   // erase the last number sign temporarily
+							MelderString_copy (& matrixName, command2.string, U'#');
+							*p = U'#';   // put the number sign back
+							p ++;   // step over last number sign
+							while (Melder_isblank (*p)) p ++;   // go to first token after matrix name
+							if (*p == U'=') {
+								/*
+									This must be an assignment to a matrix variable.
+								*/
+								p ++;   // step over equals sign
+								while (Melder_isblank (*p)) p ++;   // go to first token after assignment
+								if (*p == U'\0')
+									Melder_throw (U"Missing right-hand expression in assignment to matrix ", matrixName.string, U".");
+								struct Formula_NumericMatrix value;
+								Interpreter_numericMatrixExpression (me, p, & value);
+								InterpreterVariable var = Interpreter_lookUpVariable (me, matrixName.string);
+								NUMmatrix_free (var -> numericMatrixValue. data, 1, 1);
+								var -> numericMatrixValue = value;
+							} else if (*p == U'[') {
+								/*
+								 * This must be an assignment to an element of the matrix variable.
+								 */
+								long rowNumber = 0, columnNumber = 0;
+								p ++;   // step over opening bracket
+								/*
+									Get the row number.
+								*/
+								static MelderString rowFormula { 0 };
+								MelderString_empty (& rowFormula);
+								int depth = 0;
+								bool inString = false;
+								while ((depth > 0 || *p != U',' || inString) && *p != U'\n' && *p != U'\0') {
+									MelderString_appendCharacter (& rowFormula, *p);
+									if (*p == U'[' || *p == U'(') {
+										if (! inString) depth ++;
+									} else if (*p == U']' || *p == U')') {
+										if (! inString) depth --;
+									}
+									if (*p == U'"') inString = ! inString;
+									p ++;
+								}
+								if (*p == U'\n' || *p == U'\0')
+									Melder_throw (U"Missing comma in matrix indexing.");
+								struct Formula_Result result;
+								Interpreter_anyExpression (me, rowFormula.string, & result);
+								if (result.expressionType == kFormula_EXPRESSION_TYPE_NUMERIC) {
+									rowNumber = lround (result.result.numericResult);
+								} else {
+									Melder_throw (U"Row number should be numeric.");
+								}
+
+								p ++;   // step over comma
+								/*
+									Get the column number.
+								*/
+								static MelderString columnFormula { 0 };
+								MelderString_empty (& columnFormula);
+								depth = 0;
+								inString = false;
+								while ((depth > 0 || *p != U']' || inString) && *p != U'\n' && *p != U'\0') {
+									MelderString_appendCharacter (& columnFormula, *p);
+									if (*p == U'[') {
+										if (! inString) depth ++;
+									} else if (*p == U']') {
+										if (! inString) depth --;
+									}
+									if (*p == U'"') inString = ! inString;
+									p ++;
+								}
+								if (*p == U'\n' || *p == U'\0')
+									Melder_throw (U"Missing closing bracket (]) in matrix indexing.");
+								Interpreter_anyExpression (me, columnFormula.string, & result);
+								if (result.expressionType == kFormula_EXPRESSION_TYPE_NUMERIC) {
+									columnNumber = lround (result.result.numericResult);
+								} else {
+									Melder_throw (U"Column number should be numeric.");
+								}
+								p ++;   // step over closing bracket
+								while (Melder_isblank (*p)) p ++;
+								if (*p != U'=')
+									Melder_throw (U"Missing '=' after matrix element ", matrixName.string, U" [",
+										rowFormula.string, U",", columnFormula.string, U"].");
+								p ++;   // step over equals sign
+								while (Melder_isblank (*p)) p ++;   // go to first token after assignment
+								if (*p == U'\0') {
+									Melder_throw (U"Missing expression after matrix element ", matrixName.string, U" [",
+										rowFormula.string, U",", columnFormula.string, U"].");
+								}
+								double value;
+								Interpreter_numericExpression (me, p, & value);
+								InterpreterVariable var = Interpreter_hasVariable (me, matrixName.string);
+								if (! var)
+									Melder_throw (U"Matrix ", matrixName.string, U" does not exist.");
+								if (rowNumber < 1)
+									Melder_throw (U"A row number cannot be less than 1 (the row number you supplied is ", rowNumber, U").");
+								if (rowNumber > var -> numericMatrixValue. numberOfRows)
+									Melder_throw (U"A row number cannot be greater than the number of rows (here ",
+										var -> numericMatrixValue. numberOfRows, U"). The row number you supplied is ", rowNumber, U".");
+								if (columnNumber < 1)
+									Melder_throw (U"A column number cannot be less than 1 (the column number you supplied is ", columnNumber, U").");
+								if (columnNumber > var -> numericMatrixValue. numberOfColumns)
+									Melder_throw (U"A column number cannot be greater than the number of columns (here ",
+										var -> numericMatrixValue. numberOfColumns, U"). The column number you supplied is ", columnNumber, U".");
+								var -> numericMatrixValue. data [rowNumber] [columnNumber] = value;
+							} else Melder_throw (U"Missing '=' after matrix variable ", matrixName.string, U".");
+						} else {
+							/*
+								Assign to a numeric vector variable or to a vector element.
+							*/
+							static MelderString vectorName { 0 };
+							*p = U'\0';   // erase the number sign temporarily
+							MelderString_copy (& vectorName, command2.string, U"#");
+							*p = U'#';   // put the number sign back
+							p ++;   // step over number sign
+							while (Melder_isblank (*p)) p ++;   // go to first token after array name
+							if (*p == U'=') {
+								/*
+									This must be an assignment to a vector variable.
+								*/
+								p ++;   // step over equals sign
+								while (Melder_isblank (*p)) p ++;   // go to first token after assignment
+								if (*p == U'\0')
+									Melder_throw (U"Missing right-hand expression in assignment to vector ", vectorName.string, U".");
+								struct Formula_NumericVector value;
+								Interpreter_numericVectorExpression (me, p, & value);
+								InterpreterVariable var = Interpreter_lookUpVariable (me, vectorName.string);
+								NUMvector_free (var -> numericVectorValue. data, 1);
+								var -> numericVectorValue = value;
+							} else if (*p == U'[') {
+								/*
+								 * This must be an assignment to an element of the vector variable.
+								 */
+								long indexValue = 0;
+								p ++;   // step over opening bracket
+								static MelderString index { 0 };
+								MelderString_empty (& index);
+								int depth = 0;
+								bool inString = false;
+								while ((depth > 0 || *p != U']' || inString) && *p != U'\n' && *p != U'\0') {
+									MelderString_appendCharacter (& index, *p);
+									if (*p == U'[') {
+										if (! inString) depth ++;
+									} else if (*p == U']') {
+										if (! inString) depth --;
+									}
+									if (*p == U'"') inString = ! inString;
+									p ++;
+								}
+								if (*p == U'\n' || *p == U'\0')
+									Melder_throw (U"Missing closing bracket (]) in array element.");
+								struct Formula_Result result;
+								Interpreter_anyExpression (me, index.string, & result);
+								if (result.expressionType == kFormula_EXPRESSION_TYPE_NUMERIC) {
+									indexValue = lround (result.result.numericResult);
+								} else {
+									Melder_throw (U"Element index should be numeric.");
+								}
+								p ++;   // step over closing bracket
+								while (Melder_isblank (*p)) p ++;
+								if (*p != U'=')
+									Melder_throw (U"Missing '=' after vector element ", vectorName.string, U" [", index.string, U"].");
+								p ++;   // step over equals sign
+								while (Melder_isblank (*p)) p ++;   // go to first token after assignment
+								if (*p == U'\0') {
+									Melder_throw (U"Missing expression after vector element ", vectorName.string, U" [", index.string, U"].");
+								}
+								double value;
+								Interpreter_numericExpression (me, p, & value);
+								InterpreterVariable var = Interpreter_hasVariable (me, vectorName.string);
+								if (! var)
+									Melder_throw (U"Vector ", vectorName.string, U" does not exist.");
+								if (indexValue < 1)
+									Melder_throw (U"A vector index cannot be less than 1 (the index you supplied is ", indexValue, U").");
+								if (indexValue > var -> numericVectorValue. numberOfElements)
+									Melder_throw (U"A vector index cannot be greater than the number of elements (here ",
+										var -> numericVectorValue. numberOfElements, U"). The index you supplied is ", indexValue, U".");
+								var -> numericVectorValue. data [indexValue] = value;
+							} else Melder_throw (U"Missing '=' after vector variable ", vectorName.string, U".");
 						}
-						struct Formula_NumericArray value;
-						Interpreter_numericArrayExpression (me, p, & value);
-						InterpreterVariable var = Interpreter_lookUpVariable (me, command2.string);
-						NUMmatrix_free (var -> numericArrayValue. data, 1, 1);
-						var -> numericArrayValue = value;
 					} else {
 						/*
 						 * Try to assign to a numeric variable.
@@ -1635,7 +1778,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 							continue;   // next line
 						}
 						char32 *endOfVariable = p;
-						while (*p == U' ' || *p == U'\t') p ++;
+						while (Melder_isblank (*p)) p ++;
 						if (*p == U'=' || ((*p == U'+' || *p == U'-' || *p == U'*' || *p == U'/') && p [1] == U'=')) {
 							/*
 							 * This must be an assignment (though: "echo = ..." ???)
@@ -1654,10 +1797,15 @@ void Interpreter_run (Interpreter me, char32 *text) {
 								static MelderString index { 0 };
 								MelderString_empty (& index);
 								int depth = 0;
-								while ((depth > 0 || (*p != U',' && *p != U']')) && *p != U'\n' && *p != U'\0') {
+								bool inString = false;
+								while ((depth > 0 || (*p != U',' && *p != U']') || inString) && *p != U'\n' && *p != U'\0') {
 									MelderString_appendCharacter (& index, *p);
-									if (*p == U'[') depth ++;
-									else if (*p == U']') depth --;
+									if (*p == U'[') {
+										if (! inString) depth ++;
+									} else if (*p == U']') {
+										if (! inString) depth --;
+									}
+									if (*p == U'"') inString = ! inString;
 									p ++;
 								}
 								if (*p == U'\n' || *p == U'\0')
@@ -1672,13 +1820,13 @@ void Interpreter_run (Interpreter me, char32 *text) {
 									Melder_free (result.result.stringResult);
 								}
 								MelderString_appendCharacter (& indexedVariableName, *p);
-								if (*p == ']') {
+								if (*p == U']') {
 									break;
 								}
 							}
 							variableName = indexedVariableName.string;
 							p ++;   // skip closing bracket
-							while (*p == U' ' || *p == U'\t') p ++;
+							while (Melder_isblank (*p)) p ++;
 							if (*p == U'=' || ((*p == U'+' || *p == U'-' || *p == U'*' || *p == U'/') && p [1] == U'=')) {
 								typeOfAssignment = *p == U'+' ? 1 : *p == U'-' ? 2 : *p == U'*' ? 3 : *p == U'/' ? 4 : 0;
 							}
@@ -1841,18 +1989,25 @@ void Interpreter_numericExpression (Interpreter me, const char32 *expression, do
 	}
 }
 
-void Interpreter_stringExpression (Interpreter me, const char32 *expression, char32 **value) {
-	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_STRING, false);
+void Interpreter_numericVectorExpression (Interpreter me, const char32 *expression, struct Formula_NumericVector *value) {
+	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR, false);
 	struct Formula_Result result;
 	Formula_run (0, 0, & result);
-	*value = result. result.stringResult;
+	*value = result. result.numericVectorResult;
+}
+
+void Interpreter_numericMatrixExpression (Interpreter me, const char32 *expression, struct Formula_NumericMatrix *value) {
+	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX, false);
+	struct Formula_Result result;
+	Formula_run (0, 0, & result);
+	*value = result. result.numericMatrixResult;
 }
 
-void Interpreter_numericArrayExpression (Interpreter me, const char32 *expression, struct Formula_NumericArray *value) {
-	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY, false);
+void Interpreter_stringExpression (Interpreter me, const char32 *expression, char32 **value) {
+	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_STRING, false);
 	struct Formula_Result result;
 	Formula_run (0, 0, & result);
-	*value = result. result.numericArrayResult;
+	*value = result. result.stringResult;
 }
 
 void Interpreter_anyExpression (Interpreter me, const char32 *expression, struct Formula_Result *result) {
diff --git a/sys/Interpreter.h b/sys/Interpreter.h
index 16a5b7a..d4922aa 100644
--- a/sys/Interpreter.h
+++ b/sys/Interpreter.h
@@ -22,17 +22,14 @@
 #include "Gui.h"
 #include "Formula.h"
 
-#define USE_HASH  1
-
-#if USE_HASH
 #include <string>
 #include <unordered_map>
-#endif
 
 Thing_define (InterpreterVariable, SimpleString) {
 	char32 *stringValue;
 	double numericValue;
-	struct Formula_NumericArray numericArrayValue;
+	struct Formula_NumericVector numericVectorValue;
+	struct Formula_NumericMatrix numericMatrixValue;
 
 	void v_destroy () noexcept
 		override;
@@ -58,11 +55,7 @@ Thing_define (Interpreter, Thing) {
 	char32 labelNames [1+Interpreter_MAXNUM_LABELS] [1+Interpreter_MAX_LABEL_LENGTH];
 	long labelLines [1+Interpreter_MAXNUM_LABELS];
 	char32 dialogTitle [1+100], procedureNames [1+Interpreter_MAX_CALL_DEPTH] [100];
-	#if USE_HASH
-	std::unordered_map <std::u32string, InterpreterVariable> *variablesMap;
-	#else
-	SortedSetOfStringOf<structInterpreterVariable> variables;
-	#endif
+	std::unordered_map <std::u32string, InterpreterVariable> variablesMap;
 	bool running, stopped;
 
 	void v_destroy () noexcept
@@ -86,8 +79,9 @@ void Interpreter_stop (Interpreter me);   // can be called from any procedure ca
 
 void Interpreter_voidExpression (Interpreter me, const char32 *expression);
 void Interpreter_numericExpression (Interpreter me, const char32 *expression, double *value);
+void Interpreter_numericVectorExpression (Interpreter me, const char32 *expression, struct Formula_NumericVector *value);
+void Interpreter_numericMatrixExpression (Interpreter me, const char32 *expression, struct Formula_NumericMatrix *value);
 void Interpreter_stringExpression (Interpreter me, const char32 *expression, char32 **value);
-void Interpreter_numericArrayExpression (Interpreter me, const char32 *expression, struct Formula_NumericArray *value);
 void Interpreter_anyExpression (Interpreter me, const char32 *expression, struct Formula_Result *result);
 
 InterpreterVariable Interpreter_hasVariable (Interpreter me, const char32 *key);
diff --git a/sys/Strings.cpp b/sys/Strings.cpp
index 1a95874..7439696 100644
--- a/sys/Strings.cpp
+++ b/sys/Strings.cpp
@@ -125,7 +125,7 @@ static autoStrings Strings_createAsFileOrDirectoryList (const char32 *path /* ca
 				}
 				MelderString_copy (& right, asterisk + 1);
 			}
-			char buffer8 [1+kMelder_MAXPATH];
+			char buffer8 [kMelder_MAXPATH+1];
 			Melder_str32To8bitFileRepresentation_inline (searchDirectory. string, buffer8);
 			d = opendir (buffer8 [0] ? buffer8 : ".");
 			if (! d)
@@ -137,7 +137,7 @@ static autoStrings Strings_createAsFileOrDirectoryList (const char32 *path /* ca
 			while (!! (entry = readdir (d))) {
 				MelderString_copy (& filePath, searchDirectory. string [0] ? searchDirectory. string : U".");
 				MelderString_appendCharacter (& filePath, Melder_DIRECTORY_SEPARATOR);
-				char32 buffer32 [1+kMelder_MAXPATH];
+				char32 buffer32 [kMelder_MAXPATH+1];
 				Melder_8bitFileRepresentationToStr32_inline (entry -> d_name, buffer32);
 				MelderString_append (& filePath, buffer32);
 				//Melder_casual (U"read ", filePath. string);
@@ -171,13 +171,13 @@ static autoStrings Strings_createAsFileOrDirectoryList (const char32 *path /* ca
 		}
 	#elif defined (_WIN32)
 		try {
-			char32 searchPath [1+kMelder_MAXPATH];
+			char32 searchPath [kMelder_MAXPATH+1];
 			int len = str32len (path);
 			bool hasAsterisk = !! str32chr (path, U'*');
 			bool endsInSeparator = ( len != 0 && path [len - 1] == U'\\' );
 			autoStrings me = Thing_new (Strings);
 			my strings = NUMvector <char32 *> (1, 1000000);
-			Melder_sprint (searchPath, 1+kMelder_MAXPATH, path, hasAsterisk || endsInSeparator ? U"" : U"\\", hasAsterisk ? U"" : U"*");
+			Melder_sprint (searchPath, kMelder_MAXPATH+1, path, hasAsterisk || endsInSeparator ? U"" : U"\\", hasAsterisk ? U"" : U"*");
 			WIN32_FIND_DATAW findData;
 			HANDLE searchHandle = FindFirstFileW (Melder_peek32toW (searchPath), & findData);
 			if (searchHandle != INVALID_HANDLE_VALUE) {
diff --git a/sys/melder.h b/sys/melder.h
index 99af607..723023a 100644
--- a/sys/melder.h
+++ b/sys/melder.h
@@ -166,7 +166,7 @@ inline static int64 str32spn (const char32 *string1, const char32 *string2) {
 	char32 kar1, kar2;
 cont:
 	kar1 = * p ++;
-	for (const char32 * q = string2; (kar2 = * q ++) != 0;)
+	for (const char32 * q = string2; (kar2 = * q ++) != U'\0';)
 		if (kar2 == kar1)
 			goto cont;
 	return p - 1 - string1;
@@ -408,13 +408,13 @@ struct structMelderDir {
 typedef struct structMelderDir *MelderDir;
 
 const char32 * MelderFile_name (MelderFile file);
-char32 * MelderDir_name (MelderDir dir);
+const char32 * MelderDir_name (MelderDir dir);
 void Melder_pathToDir (const char32 *path, MelderDir dir);
 void Melder_pathToFile (const char32 *path, MelderFile file);
 void Melder_relativePathToFile (const char32 *path, MelderFile file);
-char32 * Melder_dirToPath (MelderDir dir);
+const char32 * Melder_dirToPath (MelderDir dir);
 	/* Returns a pointer internal to 'dir', like "/u/paul/praats" or "D:\Paul\Praats" */
-char32 * Melder_fileToPath (MelderFile file);
+const char32 * Melder_fileToPath (MelderFile file);
 void MelderFile_copy (MelderFile file, MelderFile copy);
 void MelderDir_copy (MelderDir dir, MelderDir copy);
 bool MelderFile_equal (MelderFile file1, MelderFile file2);
@@ -430,7 +430,7 @@ void MelderDir_getParentDir (MelderDir file, MelderDir parent);
 bool MelderDir_isDesktop (MelderDir dir);
 void MelderDir_getSubdir (MelderDir parent, const char32 *subdirName, MelderDir subdir);
 void Melder_rememberShellDirectory ();
-char32 * Melder_getShellDirectory ();
+const char32 * Melder_getShellDirectory ();
 void Melder_getHomeDir (MelderDir homeDir);
 void Melder_getPrefDir (MelderDir prefDir);
 void Melder_getTempDir (MelderDir tempDir);
diff --git a/sys/melder_files.cpp b/sys/melder_files.cpp
index 50b1729..e28361f 100644
--- a/sys/melder_files.cpp
+++ b/sys/melder_files.cpp
@@ -81,7 +81,7 @@ void Melder_rememberShellDirectory () {
 	Melder_getDefaultDir (& shellDir);
 	str32cpy (theShellDirectory, Melder_dirToPath (& shellDir));
 }
-char32 * Melder_getShellDirectory () {
+const char32 * Melder_getShellDirectory () {
 	return & theShellDirectory [0];
 }
 
@@ -168,7 +168,7 @@ const char32 * MelderFile_name (MelderFile file) {
 	#endif
 }
 
-char32 * MelderDir_name (MelderDir dir) {
+const char32 * MelderDir_name (MelderDir dir) {
 	#if defined (UNIX)
 		char32 *slash = str32rchr (dir -> path, U'/');
 		return slash ? slash + 1 : dir -> path;
@@ -260,11 +260,11 @@ void Melder_relativePathToFile (const char32 *path, MelderFile file) {
 	#endif
 }
 
-char32 * Melder_dirToPath (MelderDir dir) {
+const char32 * Melder_dirToPath (MelderDir dir) {
 	return & dir -> path [0];
 }
 
-char32 * Melder_fileToPath (MelderFile file) {
+const char32 * Melder_fileToPath (MelderFile file) {
 	return & file -> path [0];
 }
 
diff --git a/sys/praat_objectMenus.cpp b/sys/praat_objectMenus.cpp
index 9215c36..972b7ae 100644
--- a/sys/praat_objectMenus.cpp
+++ b/sys/praat_objectMenus.cpp
@@ -297,7 +297,9 @@ DO
 			Melder_information (result. result.stringResult);
 			Melder_free (result. result.stringResult);
 		} break;
-		case kFormula_EXPRESSION_TYPE_NUMERIC_ARRAY: {
+		case kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR: {
+		} break;
+		case kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX: {
 		}
 	}
 END2 }
diff --git a/sys/praat_version.h b/sys/praat_version.h
index f3e052a..83b4738 100644
--- a/sys/praat_version.h
+++ b/sys/praat_version.h
@@ -1,5 +1,5 @@
-#define PRAAT_VERSION_STR 6.0.18
-#define PRAAT_VERSION_NUM 6018
+#define PRAAT_VERSION_STR 6.0.19
+#define PRAAT_VERSION_NUM 6019
 #define PRAAT_YEAR 2016
-#define PRAAT_MONTH May
-#define PRAAT_DAY 23
+#define PRAAT_MONTH June
+#define PRAAT_DAY 13
diff --git a/test/script/arrays.praat b/test/script/arrays.praat
index f1368d6..4e866c8 100644
--- a/test/script/arrays.praat
+++ b/test/script/arrays.praat
@@ -1,48 +1,26 @@
-echo arrays...
-; writeInfoLine ("arrays...")
+writeInfoLine: "vectors and matrices..."
 
-# should give a different error:
-;a
+a# = zero#(16)
+a#[3] = 4
+assert a#[3] = 4
 
-a=5
+asserterror Vector b# does not exist.
+b# [5] = 3
 
-# should give an error:
-a+5
+asserterror A vector index cannot be less than 1 (the index you supplied is 0).
+a# [0] = 932875289
 
-a [1] = 3
-assert a [1] = 3
-printline 'a[1]'
-; appendInfoLine (a [1])
+asserterror A vector index cannot be greater than the number of elements (here 16). The index you supplied is 20.
+a# [20] = 45786457
 
-; abcdefghijklmnopqrstuvwxyz
-;a [12345678]
+assert numberOfRows (zero## (5, 6)) = 5
+assert numberOfColumns (zero## (5, 6)) = 6
+a## = zero## (5, 6)
+assert numberOfRows (a##) = 5
+assert a## [3, 4] = 0
 
-a = 7
-asserterror Missing expression after variable a[9].
-a [a+2] =
-
-;a [2]
-
-a [3], 5 = 7
-printline 'a[3,5]', 'a[3]'
-
-a [1] = 2
-b [a [1]] = 3
-assert b [a [1]] = 3
-printline 'b[2]'
-
-speaker$[1]="paul"
-printline <'speaker$[1]'>
-speaker$ [2] = "silke"
-printline <'speaker$[2]'>
-a$ = speaker$ [1] + " " + speaker$ [2]
-printline <'a$'>
-
-assert numberOfRows (zero# (5, 6)) = 5
-assert numberOfColumns (zero# (5, 6)) = 6
-a# = zero# (5, 6)
-assert numberOfRows (a#) = 5
-assert a# [3, 4] = 0
+a## [5, 6] = 567
+assert a##[5,6] = 567
 
 c# = linear# (0, 100, 101)
 assert c# [98] = 97
@@ -69,6 +47,8 @@ b = d# [99]
 c = d# [100]
 printline 'a' 'b' 'c'
 
+; q### =
+; data####
 ;e# = d# + c#
 
 ;speaker$# = empty$# [2]
diff --git a/test/script/indexedVariables.praat b/test/script/indexedVariables.praat
new file mode 100644
index 0000000..1eb03d6
--- /dev/null
+++ b/test/script/indexedVariables.praat
@@ -0,0 +1,47 @@
+writeInfoLine: "arrays..."
+
+# should give a different error:
+;a
+
+a=5
+
+# should give an error:
+a+5
+
+a [1] = 3
+assert a [1] = 3
+appendInfoLine: a [1]
+
+; abcdefghijklmnopqrstuvwxyz
+;a [12345678]
+
+a = 7
+asserterror Missing expression after variable a[9].
+a [a+2] =
+
+;a [2]
+
+a [3], 5 = 7
+printline 'a[3,5]', 'a[3]'
+
+a [1] = 2
+b [a [1]] = 3
+assert b [a [1]] = 3
+printline 'b[2]'
+
+speaker$[1]="paul"
+printline <'speaker$[1]'>
+speaker$ [2] = "silke"
+printline <'speaker$[2]'>
+a$ = speaker$ [1] + " " + speaker$ [2]
+printline <'a$'>
+
+a[2+length("h]""k")]=6
+assert a[6] = 6
+a[6]+=3
+assert a[6]=9
+
+speaker$ [1] = "JM"
+speaker$ [2] = "PB"
+
+printline OK

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/praat.git



More information about the debian-med-commit mailing list