[med-svn] [praat] 01/06: New upstream version 6.0.21

Rafael Laboissiere rafael at debian.org
Wed Nov 2 15:30:50 UTC 2016


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

rafael pushed a commit to branch master
in repository praat.

commit 6d792720d1dd3dba70e422c37408d927bf036acd
Author: Rafael Laboissiere <rafael at debian.org>
Date:   Mon Oct 17 07:33:00 2016 -0200

    New upstream version 6.0.21
---
 LPC/manual_LPC.cpp                           |   4 +-
 LPC/praat_LPC_init.cpp                       |  24 +-
 README.md                                    |  26 +-
 dwtools/Eigen_and_Matrix.cpp                 |  24 +
 dwtools/Eigen_and_Matrix.h                   |   3 +
 dwtools/FilterBank.cpp                       |   2 +-
 dwtools/Matrix_extensions.cpp                |   1 -
 dwtools/Matrix_extensions.h                  |   1 -
 dwtools/SSCP.cpp                             |   4 +-
 dwtools/Sound_and_Spectrogram_extensions.cpp |   2 +-
 dwtools/Table_extensions.cpp                 |  43 +-
 dwtools/Table_extensions.h                   |  18 +-
 dwtools/manual_KlattGrid.cpp                 |   4 +
 dwtools/manual_dwtools.cpp                   |  26 +-
 dwtools/praat_BSS_init.cpp                   |   4 +-
 dwtools/praat_DataModeler_init.cpp           |   2 +-
 dwtools/praat_David_init.cpp                 |  96 ++--
 dwtools/praat_HMM_init.cpp                   |  12 +-
 dwtools/praat_KlattGrid_init.cpp             |  14 +-
 dwtools/praat_MDS_init.cpp                   |   4 +-
 fon/ExperimentMFC.cpp                        |   3 +-
 fon/ExperimentMFC_def.h                      |   7 +-
 fon/Photo.cpp                                |   8 +-
 fon/RunnerMFC.cpp                            |  42 +-
 fon/manual_Exp.cpp                           |  60 +--
 fon/manual_tutorials.cpp                     |  12 +-
 fon/praat_Fon.cpp                            |   2 +-
 makefiles/makefile.defs.linux.barren         |  23 +
 makefiles/makefile.defs.linux.silent         |   4 +-
 num/NUM.h                                    |   8 +
 num/NUMrandom.cpp                            |   4 +
 sys/Collection.h                             |   2 +-
 sys/Formula.cpp                              | 722 ++++++++++++++++++++++++---
 sys/Graphics_colour.cpp                      |   4 +-
 sys/GuiShell.cpp                             |   3 +-
 sys/ScriptEditor.cpp                         |   7 +
 sys/ScriptEditor.h                           |   4 +-
 sys/melder_audio.cpp                         |   4 +-
 sys/melder_debug.cpp                         |   6 +-
 sys/melder_readtext.cpp                      |  18 +-
 sys/praat_version.h                          |   8 +-
 sys/sendsocket.c                             |  52 +-
 test/script/RBM.praat                        | 165 ++++++
 test/script/arrays.praat                     |   6 +
 test/script/softmax.praat                    |   6 +
 45 files changed, 1205 insertions(+), 289 deletions(-)

diff --git a/LPC/manual_LPC.cpp b/LPC/manual_LPC.cpp
index d457296..2eb3327 100644
--- a/LPC/manual_LPC.cpp
+++ b/LPC/manual_LPC.cpp
@@ -103,7 +103,7 @@ SCRIPT (5, Manual_SETTINGS_WINDOW_HEIGHT (7), U""
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (U"", U"Parabolic", 0)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (U"", U"Cubic", 1)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (U"", U"Sinc70", 0)
-	Manual_DRAW_SETTINGS_WINDOW_RANGE("Tilt line quefrency range (s)", U"0.001", U"0.0 (=end)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Tilt line quefrency range (s)", U"0.001", U"0.0 (= end)")
 	Manual_DRAW_SETTINGS_WINDOW_OPTIONMENU(U"Fit method", U"Robust")
 )
 NORMAL (U"The meaning of these settings is explained @@PowerCepstrum: Get peak prominence...|here at .")
@@ -194,7 +194,7 @@ SCRIPT (7, Manual_SETTINGS_WINDOW_HEIGHT (7), U""
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (U"", U"Parabolic", 0)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (U"", U"Cubic", 1)
 	Manual_DRAW_SETTINGS_WINDOW_RADIO (U"", U"Sinc70", 0)
-	Manual_DRAW_SETTINGS_WINDOW_RANGE (U"Tilt line quefrency range (s)", U"0.001", U"0.0 (=end)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE (U"Tilt line quefrency range (s)", U"0.001", U"0.0 (= end)")
 	Manual_DRAW_SETTINGS_WINDOW_OPTIONMENU (U"Fit method", U"Robust")
 )
 TAG (U"##Search peak in pitch range")
diff --git a/LPC/praat_LPC_init.cpp b/LPC/praat_LPC_init.cpp
index 7a8ae91..31c1985 100644
--- a/LPC/praat_LPC_init.cpp
+++ b/LPC/praat_LPC_init.cpp
@@ -118,7 +118,7 @@ FORM (PowerCepstrum_drawTiltLine, U"PowerCepstrum: Draw tilt line", U"PowerCepst
 	REAL (U"right Amplitude range (dB)", U"0.0")
 	LABEL (U"", U"Parameters for the tilt line fit")
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 1)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -212,7 +212,7 @@ END
 
 FORM (PowerCepstrum_getTiltLineSlope, U"PowerCepstrum: Get tilt line slope", 0)
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 1)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -234,7 +234,7 @@ END
 
 FORM (PowerCepstrum_getTiltLineIntercept, U"PowerCepstrum: Get tilt line intercept", 0)
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 1)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -262,7 +262,7 @@ FORM (PowerCepstrum_getPeakProminence, U"PowerCepstrum: Get peak prominence", U"
 	RADIOBUTTON (U"Cubic")
 	RADIOBUTTON (U"Sinc70")
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 1)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -285,7 +285,7 @@ END
 
 FORM (PowerCepstrum_subtractTilt_inline, U"PowerCepstrum: Subtract tilt (in-line)", 0)
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 1)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -326,7 +326,7 @@ END
 
 FORM (PowerCepstrum_subtractTilt, U"PowerCepstrum: Subtract tilt", 0)
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 1)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -449,7 +449,7 @@ END
 
 FORM (PowerCepstrogram_subtractTilt, U"PowerCepstrogram: Subtract tilt", 0)
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 2)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -468,7 +468,7 @@ END
 
 FORM (PowerCepstrogram_subtractTilt_inline, U"PowerCepstrogram: Subtract tilt (in-line)", nullptr)
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 2)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -519,7 +519,7 @@ FORM (PowerCepstrogram_getCPPS, U"PowerCepstrogram: Get CPPS", nullptr)
 	RADIOBUTTON (U"Sinc70")
 	LABEL (U"", U"Tilt line:")
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 2)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -582,7 +582,7 @@ FORM (PowerCepstrogram_to_Table_cpp, U"PowerCepstrogram: To Table (peak prominen
 	RADIOBUTTON (U"Cubic")
 	RADIOBUTTON (U"Sinc70")
 	REAL (U"left Tilt line quefrency range (s)", U"0.001")
-	REAL (U"right Tilt line quefrency range (s)", U"0.0 (=end)")
+	REAL (U"right Tilt line quefrency range (s)", U"0.0 (= end)")
 	OPTIONMENU (U"Line type", 2)
 	OPTION (U"Straight")
 	OPTION (U"Exponential decay")
@@ -743,7 +743,7 @@ DIRECT (LineSpectralFrequencies_help) Melder_help (U"LineSpectralFrequencies");
 
 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"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)
@@ -771,7 +771,7 @@ DIRECT (LPC_help) Melder_help (U"LPC"); END
 
 FORM (LPC_drawGain, U"LPC: Draw gain", U"LPC: Draw gain...")
 	REAL (U"From time (seconds)", U"0.0")
-	REAL (U"To time (seconds)", U"0.0 (=all)")
+	REAL (U"To time (seconds)", U"0.0 (= all)")
 	REAL (U"Minimum gain", U"0.0")
 	REAL (U"Maximum gain", U"0.0")
 	BOOLEAN (U"Garnish", true)
diff --git a/README.md b/README.md
index f7ebcdd..db3ab5d 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Of course, any improvements are welcomed by the authors.
 
 To download the latest source code of Praat from GitHub,
 click on the *zip* or *tar.gz* archive at the latest release,
-or clone ("fork") the praat/praat repository at any later change.
+or fork ("clone") the praat/praat repository at any later change.
 
 ### 1.3. Steps to take if you want to extend Praat
 
@@ -40,23 +40,26 @@ The code requires that your compiler supports C99 and C++11 (for e.g. `char32_t`
 
 ### 1.5. Compiling for Windows
 
-Install Cygwin (on a 64-bit computer),
+To compile Praat's Windows edition on a 64-bit Windows computer,
+install Cygwin on that computer,
 and under Cygwin install the Devel packages i686-w64-mingw32 (for 32-bit targets)
 and/or x86_64-w64-mingw32 (for 64-bit targets).
 Move the Praat sources directory somewhere in your `/home/yourname` tree.
-Go into this sources directory (where the folders `fon` and `sys` are) and type
+Go into this sources directory (where the folders `fon` and `sys` are).
+Then if you want to build Praat's 32-bit edition, type
 
     cp makefiles/makefile.defs.mingw32 ./makefile.defs
 
-if you want to build Praat's 32-bit edition, or
+or if you want to build Praat's 64-bit edition, type
 
     cp makefiles/makefile.defs.mingw64 ./makefile.defs
 
-if you want to build Praat's 64-bit edition. Then type `make` to build `Praat.exe`
-(use `make -j4` to speed this up, e.g. to use 4 processors in parallel).
+Then type `make` to build `Praat.exe`
+(use `make -j4` to speed this up, i.e. to use 4 processors in parallel).
 
-Cross-compiling for Windows: use the [MinGW](http://www.mingw.org) compiler, perhaps on a Mac or Linux computer.
-You can find toolchains for 32 and 64 bits
+Cross-compiling for Windows on a Mac or Linux computer is slightly more difficult, but not impossible.
+You can use the [MinGW](http://www.mingw.org) compiler,
+for which you can find 32- and 64-bit toolchains
 [here](http://sourceforge.net/projects/mingw-w64/files/) (look for Automated Builds).
 Install the GDI+ headers and the GDI+ library ([32-bit](http://www.fon.hum.uva.nl/praat/libgdiplus.a-32.zip);
 for 64-bit Windows just extract a GDI+ DLL from somewhere).
@@ -66,7 +69,8 @@ Then type `make`.
 
 ### 1.6. Compiling for Macintosh
 
-Extract the *xcodeproj64.zip* file from the latest release into the directory that contains
+Extract the *praatXXXX_xcodeproj64.zip* file from the [latest release](https://github.com/praat/praat/releases)
+into the directory that contains
 `sys`, `fon`, `dwtools` and so on. Then open the project `praat64.xcodeproj` in Xcode
 and choose Build or Run for the target `praat_mac64`.
 
@@ -79,7 +83,7 @@ If you get lots of errors saying “Expected unqualified-id” or “Unknown typ
 then you may have to switch the Type of some .cpp file from “C++ Source” to “Objective-C++ Source”
 (under “Identity and Type” in the righthand sidebar).
 
-If you want to build the Praat library instead of the executable,
+If you want to build Praat as a library instead of as an executable,
 try the target `praat_mac64_a` (static) or `praat_mac64_so` (dynamic).
 
 ### 1.7. Compiling on Linux and other Unixes
@@ -112,7 +116,7 @@ gmodule-2.0, gthread-2.0, rt, glib-2.0).
 ## 2. Binary executables
 
 While the [Praat website](http://www.praat.org) contains the latest executable for all platforms that we support
-(or used to support), the releases on GitHub contain many older executables as well.
+(or used to support), the [releases on GitHub](https://github.com/praat/praat/releases) contain many older executables as well.
 
 The meaning of the names of binary files available on GitHub is as follows:
 
diff --git a/dwtools/Eigen_and_Matrix.cpp b/dwtools/Eigen_and_Matrix.cpp
index f8b2084..dea02bd 100644
--- a/dwtools/Eigen_and_Matrix.cpp
+++ b/dwtools/Eigen_and_Matrix.cpp
@@ -25,6 +25,30 @@
 #include "Matrix_extensions.h"
 #include "NUM2.h"
 
+
+autoMatrix Eigen_extractEigenvector (Eigen me, long index, long numberOfRows, long numberOfColumns) {
+	try {
+		if (numberOfRows == 0 && numberOfColumns == 0) {
+			numberOfRows = 1; numberOfColumns = my dimension;
+		}
+		if (numberOfRows == 0) {
+			numberOfRows = lround (ceil ((double) my dimension / numberOfColumns));
+		} else if (numberOfColumns == 0) {
+			numberOfColumns = lround (ceil ((double) my dimension / numberOfRows));
+		}
+		autoMatrix thee = Matrix_createSimple (numberOfRows, numberOfColumns);
+		long i = 1;
+		for (long irow = 1; irow <= numberOfRows; irow++) {
+			for (long icol = 1; icol <= numberOfColumns; icol++) {
+				thy z [irow] [icol] = i <= my dimension ? my eigenvectors [index] [i++] : 0.0;
+			}
+		}
+		return thee;
+	} catch (MelderError) {
+		Melder_throw (me, U"No eigenvector extracted.");
+	}
+}
+
 autoMatrix Eigen_and_Matrix_to_Matrix_projectRows (Eigen me, Matrix thee, long numberOfDimensionsToKeep) {
 	try {
 		if (numberOfDimensionsToKeep <= 0 || numberOfDimensionsToKeep > my numberOfEigenvalues) {
diff --git a/dwtools/Eigen_and_Matrix.h b/dwtools/Eigen_and_Matrix.h
index 40d4e8f..8261b3b 100644
--- a/dwtools/Eigen_and_Matrix.h
+++ b/dwtools/Eigen_and_Matrix.h
@@ -28,6 +28,9 @@
 #include "Eigen.h"
 #include "Matrix.h"
 
+autoMatrix Eigen_extractEigenvector (Eigen me, long index, long numberOfRows, long numberOfColumns);
+/* Extract eigenvector as a (reshaped) matrix */
+
 autoMatrix Eigen_and_Matrix_to_Matrix_projectRows (Eigen me, Matrix thee, long numberOfDimensionsToKeep);
 /*
 	Purpose: project the rows of the matrix (thee) on the eigenspace (me). 
diff --git a/dwtools/FilterBank.cpp b/dwtools/FilterBank.cpp
index c5484fa..eded5fb 100644
--- a/dwtools/FilterBank.cpp
+++ b/dwtools/FilterBank.cpp
@@ -1028,7 +1028,7 @@ static int Sound_into_MelFilter_frame (Sound me, MelFilter thee, long frame) {
 		double fh_hz =  MELTOHZ (fc_mel + df);
 		double *pow = pv -> z[1];
 		for (long j = 1; j <= nf; j++) {
-			// Bin with a triangular filter the power (=amplitude-squared)
+			// Bin with a triangular filter the power (= amplitude-squared)
 
 			double f = z1 + (j - 1) * dz;
 			double a = NUMtriangularfilter_amplitude (fl_hz, fc_hz, fh_hz, f);
diff --git a/dwtools/Matrix_extensions.cpp b/dwtools/Matrix_extensions.cpp
index b71da24..5f6c14d 100644
--- a/dwtools/Matrix_extensions.cpp
+++ b/dwtools/Matrix_extensions.cpp
@@ -524,5 +524,4 @@ autoMatrix Matrix_readFromIDXFormatFile (MelderFile file) {
 	}
 }
 
-
 /* End of file Matrix_extensions.cpp */
diff --git a/dwtools/Matrix_extensions.h b/dwtools/Matrix_extensions.h
index a68038e..991bc95 100644
--- a/dwtools/Matrix_extensions.h
+++ b/dwtools/Matrix_extensions.h
@@ -64,5 +64,4 @@ double Matrix_getMean (Matrix me, double xmin, double xmax, double ymin, double
 
 double Matrix_getStandardDeviation (Matrix me, double xmin, double xmax, double ymin, double ymax);
 
-
 #endif /* _Matrix_extensions_h_ */
diff --git a/dwtools/SSCP.cpp b/dwtools/SSCP.cpp
index 1e225ad..493a3ef 100644
--- a/dwtools/SSCP.cpp
+++ b/dwtools/SSCP.cpp
@@ -209,7 +209,7 @@ autoSSCPList SSCPList_extractTwoDimensions (SSCPList me, long d1, long d2) {
 	}
 }
 
-void SSCP_drawTwoDimensionalEllipse_inside  (SSCP me, Graphics g, double scale, char32 * label, int fontSize) {
+void SSCP_drawTwoDimensionalEllipse_inside (SSCP me, Graphics g, double scale, char32 * label, int fontSize) {
 	try {
 		long nsteps = 100;
 		autoNUMvector<double> x (0L, nsteps);
@@ -278,7 +278,7 @@ static void _SSCP_drawTwoDimensionalEllipse (SSCP me, Graphics g, double scale,
 		x[i] = my centroid[1] + xt;
 	}
 	Graphics_polyline (g, nsteps + 1, x.peek(), y.peek());
-	if (fontSize > 0 && (name = Thing_getName (me))) {
+	if (fontSize > 0 && (name = Thing_getName (me)) != nullptr) {
 		int oldFontSize = Graphics_inqFontSize (g);
 		Graphics_setFontSize (g, fontSize);
 		Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
diff --git a/dwtools/Sound_and_Spectrogram_extensions.cpp b/dwtools/Sound_and_Spectrogram_extensions.cpp
index c395844..93213be 100644
--- a/dwtools/Sound_and_Spectrogram_extensions.cpp
+++ b/dwtools/Sound_and_Spectrogram_extensions.cpp
@@ -179,7 +179,7 @@ static void Sound_into_MelSpectrogram_frame (Sound me, MelSpectrogram thee, long
 		long ifrom, ito;
 		Sampled_getWindowSamples (him.get(), fl_hz, fh_hz, &ifrom, &ito);
 		for (long i = ifrom; i <= ito; i++) {
-			// Bin with a triangular filter the power (=amplitude-squared)
+			// Bin with a triangular filter the power (= amplitude-squared)
 
 			double f = his x1 + (i - 1) * his dx;
 			double a = NUMtriangularfilter_amplitude (fl_hz, fc_hz, fh_hz, f);
diff --git a/dwtools/Table_extensions.cpp b/dwtools/Table_extensions.cpp
index 10f8a08..f4ec585 100644
--- a/dwtools/Table_extensions.cpp
+++ b/dwtools/Table_extensions.cpp
@@ -3220,7 +3220,7 @@ static bool intervalsIntersect (double x1, double x2, double xmin, double xmax,
 }
 
 void Table_horizontalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
-	double ymin, double ymax, long xci_min, long xci_max, double bar_mm, int garnish, const char32 *formula, Interpreter interpreter)
+	double ymin, double ymax, long xci_min, long xci_max, double bar_mm, bool garnish, const char32 *formula, Interpreter interpreter)
 {
 	try {
 		long nrows = my rows.size;
@@ -3290,8 +3290,11 @@ void Table_horizontalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, lon
 	}
 }
 
-void Table_verticalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
-	double ymin, double ymax, long yci_min, long yci_max, double bar_mm, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_verticalErrorBarsPlotWhere (Table me, Graphics g,
+	long xcolumn, long ycolumn, double xmin, double xmax,
+	double ymin, double ymax, long yci_min, long yci_max,
+	double bar_mm, bool garnish, const char32 *formula, Interpreter interpreter)
+{
 	try {
 		long nrows = my rows.size;
 		if (xcolumn < 1 || xcolumn > nrows || ycolumn < 1 || ycolumn > nrows ||
@@ -3383,7 +3386,9 @@ double Table_getMedianAbsoluteDeviation (Table me, long columnNumber)
 		Melder_throw (me, U": cannot compute median absolute deviation of column ", columnNumber, U".");
 	}
 
-autoTable Table_getOneWayKruskalWallis (Table me, long column, long factorColumn, double *prob, double *p_kruskalWallis, double *p_df) {
+autoTable Table_getOneWayKruskalWallis (Table me, long column, long factorColumn,
+	double *prob, double *p_kruskalWallis, double *p_df)
+{
 	try {
 		if (column < 1 || column > my numberOfColumns) {
 			Melder_throw (U"Invalid column number.");
@@ -4062,7 +4067,7 @@ void Table_quantileQuantilePlot (Table me, Graphics g, long xcolumn, long ycolum
 	}
 }
 
-void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, double ymin, double ymax, int garnish) {
+void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, double ymin, double ymax, bool garnish) {
 	try {
 		if (dataColumn < 1 || dataColumn > my numberOfColumns || factorColumn < 1 || factorColumn > my numberOfColumns) return;
 		Table_numericize_Assert (me, dataColumn);
@@ -4102,7 +4107,10 @@ void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, d
 	}
 }
 
-void Table_boxPlotsWhere (Table me, Graphics g, char32 *dataColumns_string, long factorColumn, double ymin, double ymax, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_boxPlotsWhere (Table me, Graphics g,
+	char32 *dataColumns_string, long factorColumn, double ymin, double ymax,
+	bool garnish, const char32 *formula, Interpreter interpreter)
+{
 	try {
 		long numberOfSelectedColumns;
 		autoNUMvector<long> dataColumns (Table_getColumnIndicesFromColumnLabelString (me, dataColumns_string, & numberOfSelectedColumns), 1);
@@ -4165,7 +4173,10 @@ void Table_boxPlotsWhere (Table me, Graphics g, char32 *dataColumns_string, long
 	}
 }
 
-void Table_distributionPlotWhere (Table me, Graphics g, long dataColumn, double minimum, double maximum, long nBins, double freqMin, double freqMax, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_distributionPlotWhere (Table me, Graphics g,
+	long dataColumn, double minimum, double maximum, long nBins, double freqMin, double freqMax,
+	bool garnish, const char32 *formula, Interpreter interpreter)
+{
 	try {
 		if (dataColumn < 1 || dataColumn > my numberOfColumns) return;
 		Formula_compile (interpreter, me, formula, kFormula_EXPRESSION_TYPE_UNKNOWN, true);
@@ -4281,7 +4292,11 @@ long *Table_findRowsMatchingCriterion (Table me, const char32 *formula, Interpre
 }
 
 
-void Table_barPlotWhere (Table me, Graphics g, const char32 *columnLabels, double ymin, double ymax, const char32 *factorColumn, double xoffsetFraction, double interbarFraction, double interbarsFraction, const char32 *colours, double angle, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_barPlotWhere (Table me, Graphics g,
+	const char32 *columnLabels, double ymin, double ymax, const char32 *factorColumn,
+	double xoffsetFraction, double interbarFraction, double interbarsFraction, const char32 *colours,
+	double angle, bool garnish, const char32 *formula, Interpreter interpreter)
+{
 	try {
 		long numberOfColumns, numberOfRowMatches = 0;
 		autoNUMvector<long> columnIndex (Table_getColumnIndicesFromColumnLabelString (me, columnLabels, &numberOfColumns), 1);
@@ -4392,7 +4407,10 @@ static int Graphics_getConnectingLine (Graphics g, const char32 *text1, double x
 }
 
 // take the xcolumn as labels if non-numeric column else as numbers and arrange distances accordingly.
-void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, double xmax, long ycolumn, double ymin, double ymax, const char32 *symbol, double angle, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_lineGraphWhere (Table me, Graphics g,
+	long xcolumn, double xmin, double xmax, long ycolumn, double ymin, double ymax,
+	const char32 *symbol, double angle, bool garnish, const char32 *formula, Interpreter interpreter)
+{
 	try {
 		if (ycolumn < 1 || ycolumn > my numberOfColumns) return;
 		long numberOfSelectedRows = 0;
@@ -4482,7 +4500,10 @@ void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, doub
 	}
 }
 
-void Table_lagPlotWhere (Table me, Graphics g, long column, long lag, double xmin, double xmax, const char32 *symbol, int labelSize, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_lagPlotWhere (Table me, Graphics g,
+	long column, long lag, double xmin, double xmax, const char32 *symbol, int labelSize,
+	bool garnish, const char32 *formula, Interpreter interpreter)
+{
 	try {
 		if (column < 1 || column > my rows.size) {
 			return;
@@ -4647,7 +4668,7 @@ autoTable Table_extractMahalanobisWhere(Table me, const char32 *columnLabels, co
 	}
 }
 
-void Table_drawEllipsesWhere (Table me, Graphics g, long xcolumn, long ycolumn, long factorColumn, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, long labelSize, int garnish, const char32 *formula, Interpreter interpreter) {
+void Table_drawEllipsesWhere (Table me, Graphics g, long xcolumn, long ycolumn, long factorColumn, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, long labelSize, bool garnish, const char32 *formula, Interpreter interpreter) {
 	try {
 		long numberOfSelectedRows = 0;
 		autoNUMvector<long> selectedRows (Table_findRowsMatchingCriterion (me, formula, interpreter, &numberOfSelectedRows), 1);	
diff --git a/dwtools/Table_extensions.h b/dwtools/Table_extensions.h
index 0385acd..af77c8c 100644
--- a/dwtools/Table_extensions.h
+++ b/dwtools/Table_extensions.h
@@ -54,10 +54,10 @@ autoTable Table_getOneWayKruskalWallis (Table me, long column, long factorColumn
 autoTable Table_getTwoWayAnalysisOfVarianceF (Table me, long column, long groupColumnA, long groupColumnB, autoTable *means, autoTable *factorLevelSizes);
 
 void Table_verticalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
-	double ymin, double ymax, long yci_min, long yci_max, double bar_mm, int garnish, const char32 *formula, Interpreter interpreter);
+	double ymin, double ymax, long yci_min, long yci_max, double bar_mm, bool garnish, const char32 *formula, Interpreter interpreter);
 
 void Table_horizontalErrorBarsPlotWhere (Table me, Graphics g, long xcolumn, long ycolumn, double xmin, double xmax, 
-	double ymin, double ymax, long xci_min, long xci_max, double bar_mm, int garnish, const char32 *formula, Interpreter interpreter);
+	double ymin, double ymax, long xci_min, long xci_max, double bar_mm, bool garnish, const char32 *formula, Interpreter interpreter);
 
 void Table_normalProbabilityPlot (Table me, Graphics g, long column, long numberOfQuantiles, double numberOfSigmas, int labelSize, const char32 *label, bool garnish);
 
@@ -65,9 +65,9 @@ void Table_quantileQuantilePlot (Table me, Graphics g, long xcolumn, long ycolum
 
 void Table_quantileQuantilePlot_betweenLevels (Table me, Graphics g, long dataColumn, long factorColumn, const char32 *xlevel, const char32 *ylevel, long numberOfQuantiles, double xmin, double xmax, double ymin, double ymax, int labelSize, const char32 *label, bool garnish);
 
-void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, double ymin, double ymax, int garnish);
+void Table_boxPlots (Table me, Graphics g, long dataColumn, long factorColumn, double ymin, double ymax, bool garnish);
 
-void Table_boxPlotsWhere (Table me, Graphics g, char32 *dataColumns_string, long factorColumn, double ymin, double ymax, int garnish, const char32 *formula, Interpreter interpreter);
+void Table_boxPlotsWhere (Table me, Graphics g, char32 *dataColumns_string, long factorColumn, double ymin, double ymax, bool garnish, const char32 *formula, Interpreter interpreter);
 
 autoTable Table_extractRowsWhere (Table me, const char32 *formula, Interpreter interpreter);
 
@@ -75,15 +75,15 @@ autoTable Table_extractColumnRanges (Table me, char32 *ranges);
 
 autoTable Table_extractMahalanobisWhere (Table me, const char32 *columnLabels, const char32 *factorColumn, double numberOfSigmas, int which_Melder_NUMBER, const char32 *formula, Interpreter interpreter);
 
-void Table_distributionPlotWhere (Table me, Graphics g, long dataColumn, double minimum, double maximum, long nBins, double freqMin, double freqMax, int garnish, const char32 *formula, Interpreter interpreter);
+void Table_distributionPlotWhere (Table me, Graphics g, long dataColumn, double minimum, double maximum, long nBins, double freqMin, double freqMax, bool garnish, const char32 *formula, Interpreter interpreter);
 
-void Table_barPlotWhere (Table me, Graphics g, const char32 *columnLabels, double ymin, double ymax, const char32 *labelColumn, double xoffsetFraction, double interbarFraction, double interbarsFraction, const char32 *colours, double angle, int garnish, const char32 *formula, Interpreter interpreter);
+void Table_barPlotWhere (Table me, Graphics g, const char32 *columnLabels, double ymin, double ymax, const char32 *labelColumn, double xoffsetFraction, double interbarFraction, double interbarsFraction, const char32 *colours, double angle, bool garnish, const char32 *formula, Interpreter interpreter);
 
-void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, double xmax, long ycolumn, double ymin, double ymax, const char32 *symbol, double angle, int garnish, const char32 *formula, Interpreter interpreter);
+void Table_lineGraphWhere (Table me, Graphics g, long xcolumn, double xmin, double xmax, long ycolumn, double ymin, double ymax, const char32 *symbol, double angle, bool garnish, const char32 *formula, Interpreter interpreter);
 
-void Table_lagPlotWhere (Table me, Graphics g, long column, long lag, double xmin, double xmax, const char32 *symbol, int labelSize, int garnish, const char32 *formula, Interpreter interpreter);
+void Table_lagPlotWhere (Table me, Graphics g, long column, long lag, double xmin, double xmax, const char32 *symbol, int labelSize, bool garnish, const char32 *formula, Interpreter interpreter);
 
-void Table_drawEllipsesWhere (Table me, Graphics g, long xcolumn, long ycolumn, long labelcolumn, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, long labelSize, int garnish, const char32 *formula, Interpreter interpreter);
+void Table_drawEllipsesWhere (Table me, Graphics g, long xcolumn, long ycolumn, long labelcolumn, double xmin, double xmax, double ymin, double ymax, double numberOfSigmas, long labelSize, bool garnish, const char32 *formula, Interpreter interpreter);
 
 void Table_printAsAnovaTable (Table me);
 
diff --git a/dwtools/manual_KlattGrid.cpp b/dwtools/manual_KlattGrid.cpp
index d09c534..7c6f560 100644
--- a/dwtools/manual_KlattGrid.cpp
+++ b/dwtools/manual_KlattGrid.cpp
@@ -190,6 +190,10 @@ MAN_BEGIN (U"KlattGrid: Extract oral formant grid (open phases)...", U"djmw", 20
 INTRO (U"Extracts the oral formant grid as used in the synthesis, i.e. the resulting grid contains the informantion from the oral formant grid and the delta formant grid combined during the open phase of the glottis. ")
 MAN_END
 
+MAN_BEGIN (U"KlattTable", U"djmw", 20160601)
+INTRO (U"The parameters for the Klatt synthesizer in table format. You can convert it to a @@KlattGrid@ which is easier to view and edit.")
+MAN_END
+
 MAN_BEGIN (U"Sound: To KlattGrid (simple)...", U"djmw", 20090415)
 INTRO (U"Create a @@KlattGrid@ from a @@Sound at .")
 ENTRY (U"Algorithm")
diff --git a/dwtools/manual_dwtools.cpp b/dwtools/manual_dwtools.cpp
index d81bbfd..f645f1c 100644
--- a/dwtools/manual_dwtools.cpp
+++ b/dwtools/manual_dwtools.cpp
@@ -3482,8 +3482,8 @@ INTRO (U"A command to draw only those parts of a @Sound where a condition holds.
 ENTRY (U"Settings")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (5), U""
 	Manual_DRAW_SETTINGS_WINDOW ("Sound: Draw where...", 5)
-	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", "0.0", "0.0 (=all)")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=all)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", "0.0", "0.0 (= all)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (= all)")
 	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish", 1)
 	Manual_DRAW_SETTINGS_WINDOW_OPTIONMENU("Drawing method", "Curve")
 	Manual_DRAW_SETTINGS_WINDOW_TEXT ("Draw only those parts where the following condition holds",
@@ -3639,8 +3639,8 @@ ENTRY (U"Settings")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (6), U""
 	Manual_DRAW_SETTINGS_WINDOW ("Sound: Paint where...", 6)
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Colour (0-1, name, {r,g,b})", "0.5")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", "0.0", "0.0 (=all)")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=all)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", "0.0", "0.0 (= all)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (= all)")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Fill from level", "0")
 	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish", 1)
 	Manual_DRAW_SETTINGS_WINDOW_TEXT ("Paint only those parts where the following condition holds",
@@ -3707,8 +3707,8 @@ ENTRY (U"Settings")
 SCRIPT (5.4, Manual_SETTINGS_WINDOW_HEIGHT (4), U""
 	Manual_DRAW_SETTINGS_WINDOW ("Sounds: Paint enclosed", 4)
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Colour (0-1, name, {r,g,b})", "0.5")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", "0.0", "0.0 (=all)")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=all)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Time range (s)", "0.0", "0.0 (= all)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (= all)")
 	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish", 1)
 )
 TAG (U"##Colour")
@@ -4196,7 +4196,7 @@ ENTRY (U"Settings")
 SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (10), U""
 	Manual_DRAW_SETTINGS_WINDOW ("Table: Bar plot where", 10)
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Vertical column(s)", "")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE("Vertical range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE("Vertical range", "0.0", "0.0 (= autoscaling)")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Column with labels", "")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Distance of first bar from border", "1.0")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Distance between bar groups", "1.0")
@@ -4288,13 +4288,13 @@ ENTRY (U"Settings")
 SCRIPT (7, Manual_SETTINGS_WINDOW_HEIGHT (8), U""
 	Manual_DRAW_SETTINGS_WINDOW ("Table: Line graph where", 8)
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Vertical column", "")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (= autoscaling)")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Horizontal column", "")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Horizontal range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Horizontal range", "0.0", "0.0 (= autoscaling)")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Text", "+")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Label text angle (degrees)", "0.0")
 	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish", 1)
-	Manual_DRAW_SETTINGS_WINDOW_TEXT("Formula:", "1; (=everything)")
+	Manual_DRAW_SETTINGS_WINDOW_TEXT("Formula:", "1; (= everything)")
 )
 TAG (U"##Vertical column")
 DEFINITION (U"The column whose data points you want to plot.")
@@ -4391,14 +4391,14 @@ ENTRY (U"Settings")
 SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (9), U""
 	Manual_DRAW_SETTINGS_WINDOW ("Table: Vertical confidence intervals plot where", 9)
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Horizontal column", "")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Horizontal range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Horizontal range", "0.0", "0.0 (= autoscaling)")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Vertical column", "")
-	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (=autoscaling)")
+	Manual_DRAW_SETTINGS_WINDOW_RANGE ("Vertical range", "0.0", "0.0 (= autoscaling)")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Lower error value column", "")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Upper error value column", "")
 	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Bar size (mm)", "1.0")
 	Manual_DRAW_SETTINGS_WINDOW_BOOLEAN("Garnish", 1)
-	Manual_DRAW_SETTINGS_WINDOW_TEXT("Formula", "1; (=everything)")
+	Manual_DRAW_SETTINGS_WINDOW_TEXT("Formula", "1; (= everything)")
 )
 TAG (U"##Horizontal column")
 DEFINITION (U"determines the data along the horizontal axis.")
diff --git a/dwtools/praat_BSS_init.cpp b/dwtools/praat_BSS_init.cpp
index 17770d6..7ae0a20 100644
--- a/dwtools/praat_BSS_init.cpp
+++ b/dwtools/praat_BSS_init.cpp
@@ -140,7 +140,7 @@ DO
 END
 
 FORM (EEG_and_PCA_to_EEG_principalComponents, U"EEG & PCA: To EEG (principal components)", U"EEG & PCA: To EEG (principal components)...")
-	INTEGER (U"Number of components", U"0 (=all)")
+	INTEGER (U"Number of components", U"0 (= all)")
 	OK
 DO
 	EEG me = FIRST (EEG);
@@ -150,7 +150,7 @@ DO
 END
 
 FORM (EEG_and_PCA_to_EEG_whiten, U"EEG & PCA: To EEG (whiten)", U"EEG & PCA: To EEG (whiten)...")
-	INTEGER (U"Number of components", U"0 (=all)")
+	INTEGER (U"Number of components", U"0 (= all)")
 	OK
 DO
 	EEG me = FIRST (EEG);
diff --git a/dwtools/praat_DataModeler_init.cpp b/dwtools/praat_DataModeler_init.cpp
index 4a7ab11..df490d5 100644
--- a/dwtools/praat_DataModeler_init.cpp
+++ b/dwtools/praat_DataModeler_init.cpp
@@ -1379,7 +1379,7 @@ END
 
 FORM (Table_to_DataModeler, U"", nullptr)
 	REAL (U"left X range", U"0.0")
-	REAL (U"right X range", U"0.0 (=auto)")
+	REAL (U"right X range", U"0.0 (= auto)")
 	WORD (U"Column with X data", U"")
 	WORD (U"Column with Y data", U"")
 	WORD (U"Column with sigmas", U"")
diff --git a/dwtools/praat_David_init.cpp b/dwtools/praat_David_init.cpp
index 514d9fd..7b5294f 100644
--- a/dwtools/praat_David_init.cpp
+++ b/dwtools/praat_David_init.cpp
@@ -622,7 +622,7 @@ DO
 		iam (CCA);
 		double p, chisq, df;
 		CCA_getZeroCorrelationProbability (me, GET_INTEGER (U"Coefficient number"), & p, & chisq, & df);
-		Melder_information (p, U" (=probability for chisq = ", chisq,
+		Melder_information (p, U" (= probability for chisq = ", chisq,
 			U" and ndf = ", df, U")");
 	}
 END
@@ -683,7 +683,7 @@ DIRECT (CCA_and_TableOfReal_factorLoadings)
 END
 
 FORM (CCA_and_TableOfReal_scores, U"CCA & TableOfReal: To TableOfReal (scores)", U"CCA & TableOfReal: To TableOfReal (scores)...")
-	INTEGER (U"Number of canonical correlations", U"0 (=all)")
+	INTEGER (U"Number of canonical correlations", U"0 (= all)")
 	OK
 DO
 	CCA me = FIRST (CCA);
@@ -880,7 +880,7 @@ END
 FORM (Confusion_condense, U"Confusion: Condense", U"Confusion: Condense...")
 	SENTENCE (U"Search", U"^(u|i)$")
 	SENTENCE (U"Replace", U"high")
-	INTEGER (U"Replace limit", U"0 (=unlimited)")
+	INTEGER (U"Replace limit", U"0 (= unlimited)")
 	RADIO (U"Search and replace are", 2)
 	RADIOBUTTON (U"Literals")
 	RADIOBUTTON (U"Regular Expressions")
@@ -897,7 +897,7 @@ END
 FORM (Confusion_group, U"Confusion: Group stimuli & responses", U"Confusion: Group...")
 	SENTENCE (U"Stimuli & Responses", U"u i")
 	SENTENCE (U"New label", U"high")
-	INTEGER (U"New label position", U"0 (=at start)")
+	INTEGER (U"New label position", U"0 (= at start)")
 	OK
 DO
 	const char32 *newlabel = GET_STRING (U"New label");
@@ -976,7 +976,7 @@ END
 /******************* Confusion & Matrix *************************************/
 
 FORM (Confusion_Matrix_draw, U"Confusion & Matrix: Draw confusions with arrows", nullptr)
-	INTEGER (U"Category position", U"0 (=all)")
+	INTEGER (U"Category position", U"0 (= all)")
 	REAL (U"Lower level (%)", U"0")
 	REAL (U"left Horizontal range", U"0.0")
 	REAL (U"right Horizontal range", U"0.0")
@@ -1074,7 +1074,7 @@ DO
 		iam (Correlation);
 		double chisq, p, df;
 		Correlation_testDiagonality_bartlett (me, nc, & chisq, & p, & df);
-		Melder_information (p, U" (=probability, based on chisq = ", chisq, U" and ndf = ", df, U")");
+		Melder_information (p, U" (= probability, based on chisq = ", chisq, U" and ndf = ", df, U")");
 	}
 END
 
@@ -1128,7 +1128,7 @@ DO
 		iam (Covariance);
 		double p, t, ndf;
 		Covariance_getSignificanceOfOneMean (me, GET_INTEGER (U"Index"), GET_REAL (U"Value"), & p, & t, & ndf);
-		Melder_information (p, U" (=probability, based on t = ", t, U" and ndf = ", ndf);
+		Melder_information (p, U" (= probability, based on t = ", t, U" and ndf = ", ndf);
 	}
 END
 
@@ -1149,7 +1149,7 @@ DO
 		double p, t, ndf;
 		Covariance_getSignificanceOfMeansDifference (me, GET_INTEGER (U"Index1"), GET_INTEGER (U"Index2"),
 			GET_REAL (U"Value"), GET_INTEGER (U"Paired"), GET_INTEGER (U"Equal variances"), & p, & t, & ndf);
-		Melder_information (p, U" (=probability, based on t = ",
+		Melder_information (p, U" (= probability, based on t = ",
 			t, U"and ndf = ", ndf, U")");
 	}
 END
@@ -1166,7 +1166,7 @@ DO
 		iam (Covariance);
 		double p, chisq; long ndf;
 		Covariance_getSignificanceOfOneVariance (me, GET_INTEGER (U"Index"), GET_REAL (U"Value"), & p, & chisq, & ndf);
-		Melder_information (p, U" (=probability, based on chisq = ", chisq, U" and ndf = ", ndf);
+		Melder_information (p, U" (= probability, based on chisq = ", chisq, U" and ndf = ", ndf);
 	}
 END
 
@@ -1184,7 +1184,7 @@ DO
 		double p, f, df;
 		Covariance_getSignificanceOfVariancesRatio (me, GET_INTEGER (U"Index1"), GET_INTEGER (U"Index2"),
 			GET_REAL (U"Value"), & p, & f , & df);
-		Melder_information (p, U" (=probability, based on F = ", f, U" and ndf1 = ", df, U" and ndf2 = ", df);
+		Melder_information (p, U" (= probability, based on F = ", f, U" and ndf1 = ", df, U" and ndf2 = ", df);
 
 	}
 END
@@ -1446,7 +1446,7 @@ DO
 		iam (Discriminant);
 		double p, chisq, df;
 		Discriminant_getPartialDiscriminationProbability (me, n, & p, & chisq, & df);
-		Melder_information (p, U" (=probability, based on chisq = ", chisq, U"and ndf = ", df, U")");
+		Melder_information (p, U" (= probability, based on chisq = ", chisq, U"and ndf = ", df, U")");
 	}
 END
 
@@ -1455,7 +1455,7 @@ DIRECT (Discriminant_getHomegeneityOfCovariances_box)
 		iam (Discriminant);
 		double chisq, p; double ndf;
 		SSCPList_getHomegeneityOfCovariances_box (my groups.get(), &p, &chisq, &ndf);
-		Melder_information (p, U" (=probability, based on chisq = ",
+		Melder_information (p, U" (= probability, based on chisq = ",
 			chisq, U"and ndf = ", ndf, U")");
 	}
 END
@@ -1820,7 +1820,7 @@ DIRECT (Discriminant_getNumberOfGroups)
 END
 
 FORM (Discriminant_getNumberOfObservations, U"Discriminant: Get number of observations", U"Discriminant: Get number of observations...")
-	INTEGER (U"Group", U"0 (=total)")
+	INTEGER (U"Group", U"0 (= total)")
 	OK
 DO
 	LOOP {
@@ -3607,9 +3607,9 @@ END
 
 FORM (old_FormantGrid_draw, U"FormantGrid: Draw", 0)
 	REAL (U"left Time range (s)", U"0.0")
-	REAL (U"right Time range (s)", U"0.0 (=all)")
+	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"0.0 (=auto)")
+	REAL (U"right Frequency range (Hz)", U"0.0 (= auto)")
 	BOOLEAN (U"Bandwidths", false)
 	BOOLEAN (U"Garnish", true)
 	OK
@@ -3625,9 +3625,9 @@ END
 
 FORM (FormantGrid_draw, U"FormantGrid: Draw", nullptr)
 	REAL (U"left Time range (s)", U"0.0")
-	REAL (U"right Time range (s)", U"0.0 (=all)")
+	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"0.0 (=auto)")
+	REAL (U"right Frequency range (Hz)", U"0.0 (= auto)")
 	BOOLEAN (U"Bandwidths", false)
 	BOOLEAN (U"Garnish", true)
 	LABEL (U"", U"")
@@ -5007,7 +5007,7 @@ DIRECT (PCA_and_Configuration_to_TableOfReal_reconstruct)
 END
 
 FORM (PCA_and_TableOfReal_to_TableOfReal_projectRows, U"PCA & TableOfReal: To TableOfReal (project rows)", U"PCA & TableOfReal: To Configuration...")
-	INTEGER (U"Number of dimensions to keep", U"0 (=all)")
+	INTEGER (U"Number of dimensions to keep", U"0 (= all)")
 	OK
 DO
 	long dimension = GET_INTEGER (U"Number of dimensions to keep");
@@ -5021,7 +5021,7 @@ DO
 END
 
 FORM (PCA_and_TableOfReal_to_Configuration, U"PCA & TableOfReal: To Configuration", U"PCA & TableOfReal: To Configuration...")
-	INTEGER (U"Number of dimensions", U"0 (=all)")
+	INTEGER (U"Number of dimensions", U"0 (= all)")
 	OK
 DO
 	long dimension = GET_INTEGER (U"Number of dimensions");
@@ -5035,7 +5035,7 @@ DO
 END
 
 FORM (PCA_and_TableOfReal_to_TableOfReal_zscores, U"PCA & TableOfReal: To TableOfReal (z-scores)", U"PCA & TableOfReal: To TableOfReal (z-scores)...")
-	INTEGER (U"Number of dimensions", U"0 (=all)")
+	INTEGER (U"Number of dimensions", U"0 (= all)")
 	OK
 DO
 	long dimension = GET_INTEGER (U"Number of dimensions");
@@ -5049,7 +5049,7 @@ DO
 END
 
 FORM (PCA_and_Matrix_to_Matrix_projectRows, U"PCA & Matrix: To Matrix (project rows)", U"")
-	INTEGER (U"Number of dimensions", U"0 (=all)")
+	INTEGER (U"Number of dimensions", U"0 (= all)")
 	OK
 DO
 	long dimension = GET_INTEGER (U"Number of dimensions");
@@ -5063,7 +5063,7 @@ DO
 END
 
 FORM (PCA_and_Matrix_to_Matrix_projectColumns, U"PCA & Matrix: To Matrix (project columns)", U"")
-	INTEGER (U"Number of dimensions", U"0 (=all)")
+	INTEGER (U"Number of dimensions", U"0 (= all)")
 	OK
 DO
 	long dimension = GET_INTEGER (U"Number of dimensions");
@@ -5101,7 +5101,7 @@ DO
 		double p, chisq, df;
 		PCA_getEqualityOfEigenvalues (me, GET_INTEGER (U"left Eigenvalue range"),
 		GET_INTEGER (U"right Eigenvalue range"), GET_INTEGER (U"Conservative test"), & p, & chisq, & df);
-		Melder_information (p, U" (=probability, based on chisq = ",
+		Melder_information (p, U" (= probability, based on chisq = ",
 		chisq, U" and df = ", df);
 	}
 END
@@ -5150,6 +5150,25 @@ DO
 	}
 END
 
+FORM (PCA_extractEigenvector, U"PCA: Extract eigenvector", nullptr)
+	NATURAL (U"Eigenvector number", U"1")
+	LABEL (U"", U"Reshape as")
+	INTEGER (U"Number of rows", U"0")
+	INTEGER (U"Number of columns", U"0")
+	OK
+DO
+	long numberOfRows = GET_INTEGER (U"Number of rows");
+	long numberOfColumns = GET_INTEGER (U"Number of columns");
+	long index = GET_INTEGER (U"Eigenvector number");
+	REQUIRE (numberOfRows >= 0, U"Number of rows must be >= 0.")
+	REQUIRE (numberOfColumns >= 0, U"Number of columns must be >= 0.")
+	LOOP {
+		iam (PCA);
+		autoMatrix thee = Eigen_extractEigenvector (me, index, numberOfRows, numberOfColumns);
+		praat_new (thee.move(), my name, U"_ev", index);
+	}
+END
+
 FORM (PCA_to_TableOfReal_reconstruct1, U"PCA: To TableOfReal (reconstruct)", U"PCA: To TableOfReal (reconstruct 1)...")
 	SENTENCE (U"Coefficients", U"1.0 1.0")
 	OK
@@ -5187,7 +5206,7 @@ DIRECT (PCAs_getAngleBetweenPc1Pc2Plane_degrees)
 	}
 	Melder_assert (p1 && p2);
 	Melder_information (Eigens_getAngleBetweenEigenplanes_degrees (p1, p2),
-		U" degrees (=angle of intersection between the two pc1-pc2 eigenplanes)");
+		U" degrees (= angle of intersection between the two pc1-pc2 eigenplanes)");
 END
 
 /******************* Permutation **************************************/
@@ -6098,8 +6117,8 @@ END
 
 FORM (Sound_and_Pitch_changeGender, U"Sound & Pitch: Change gender", U"Sound & Pitch: Change gender...")
 	POSITIVE (U"Formant shift ratio", U"1.2")
-	REAL (U"New pitch median (Hz)", U"0.0 (=no change)")
-	POSITIVE (U"Pitch range factor", U"1.0 (=no change)")
+	REAL (U"New pitch median (Hz)", U"0.0 (= no change)")
+	POSITIVE (U"Pitch range factor", U"1.0 (= no change)")
 	POSITIVE (U"Duration factor", U"1.0")
 	OK
 DO
@@ -6113,7 +6132,7 @@ END
 FORM (Sound_and_Pitch_changeSpeaker, U"Sound & Pitch: Change speaker", U"Sound & Pitch: Change speaker...")
 	POSITIVE (U"Multiply formants by", U"1.1 (male->female)")
 	POSITIVE (U"Multiply pitch by", U"1.8 (male->female")
-	REAL (U"Multiply pitch range by", U"1.0 (=no change)")
+	REAL (U"Multiply pitch range by", U"1.0 (= no change)")
 	POSITIVE (U"Multiply duration", U"1.0")
 	OK
 DO
@@ -6539,7 +6558,7 @@ FORM (Sound_to_KlattGrid_simple, U"Sound: To KlattGrid (simple)", U"Sound: To Kl
 	POSITIVE (U"Time step (s)", U"0.005")
 	LABEL (U"", U"Formant determination")
 	NATURAL (U"Max. number of formants", U"5")
-	POSITIVE (U"Maximum formant (Hz)", U"5500 (=adult female)")
+	POSITIVE (U"Maximum formant (Hz)", U"5500 (= adult female)")
 	POSITIVE (U"Window length (s)", U"0.025")
 	POSITIVE (U"Pre-emphasis from (Hz)", U"50.0")
 	LABEL (U"", U"Pitch determination")
@@ -6667,7 +6686,7 @@ FORM (Sound_changeSpeaker, U"Sound: Change speaker", U"Sound: Change speaker..."
 	LABEL (U"", U"Modification parameters")
 	POSITIVE (U"Multiply formants by", U"1.2")
 	POSITIVE (U"Multiply pitch by", U"1.0")
-	REAL (U"Multiply pitch range by", U"1.0 (=no change)")
+	REAL (U"Multiply pitch range by", U"1.0 (= no change)")
 	POSITIVE (U"Multiply duration by", U"1.0")
 	OK
 DO
@@ -6690,8 +6709,8 @@ FORM (Sound_changeGender, U"Sound: Change gender", U"Sound: Change gender...")
 	POSITIVE (U"Pitch ceiling (Hz)", U"600.0")
 	LABEL (U"", U"Modification parameters")
 	POSITIVE (U"Formant shift ratio", U"1.2")
-	REAL (U"New pitch median (Hz)", U"0.0 (=no change)")
-	REAL (U"Pitch range factor", U"1.0 (=no change)")
+	REAL (U"New pitch median (Hz)", U"0.0 (= no change)")
+	REAL (U"Pitch range factor", U"1.0 (= no change)")
 	POSITIVE (U"Duration factor", U"1.0")
 	OK
 DO
@@ -7297,7 +7316,7 @@ DO
 	LOOP {
 		iam (SSCP);
 		SSCP_testDiagonality_bartlett (me, nc, & chisq, & p, & df);
-		Melder_information (p, U" (=probability for chisq = ", chisq, U" and ndf = ", df, U")");
+		Melder_information (p, U" (= probability for chisq = ", chisq, U" and ndf = ", df, U")");
 	}
 END
 
@@ -7368,7 +7387,7 @@ END
 FORM (Strings_change, U"Strings: Change", U"Strings: Change")
 	SENTENCE (U"Search", U"a")
 	SENTENCE (U"Replace", U"a")
-	INTEGER (U"Replace limit", U"0 (=unlimited)")
+	INTEGER (U"Replace limit", U"0 (= unlimited)")
 	RADIO (U"Search and replace are:", 1)
 	RADIOBUTTON (U"Literals")
 	RADIOBUTTON (U"Regular Expressions")
@@ -7426,7 +7445,7 @@ END
 
 FORM (SVD_to_TableOfReal, U"SVD: To TableOfReal", U"SVD: To TableOfReal...")
 	NATURAL (U"First component", U"1")
-	INTEGER (U"Last component", U"0 (=all)")
+	INTEGER (U"Last component", U"0 (= all)")
 	OK
 DO
 	LOOP {
@@ -7657,7 +7676,7 @@ FORM (Table_drawEllipses, U"Table: Draw ellipses", nullptr)
 	REAL (U"right Vertical range", U"0.0 (= auto)")
 	WORD (U"Factor column", U"Vowel")
 	POSITIVE (U"Number of sigmas", U"1.0")
-	NATURAL (U"Font size", U"12")
+	INTEGER (U"Font size", U"12 (0 = no label)")
 	BOOLEAN (U"Garnish", true)
 	OK
 DO
@@ -7682,7 +7701,7 @@ FORM (Table_drawEllipsesWhere, U"Table: Draw ellipses where", nullptr)
 	REAL (U"right Vertical range", U"0.0 (= auto)")
 	WORD (U"Factor column", U"Vowel")
 	POSITIVE (U"Number of sigmas", U"1.0")
-	NATURAL (U"Font size", U"12")
+	INTEGER (U"Font size", U"12 (0 = no label)")
 	BOOLEAN (U"Garnish", true)
 	LABEL (U"", U"Use only data in rows where the following condition holds:")
 	TEXTFIELD (U"Formula", U"1; self$[\"gender\"]=\"male\"")
@@ -8502,9 +8521,9 @@ END
 
 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"right Row range", U"0 (= all)")
 	INTEGER (U"left Column range", U"0")
-	INTEGER (U"right Column range", U"0 (=all)")
+	INTEGER (U"right Column range", U"0 (= all)")
 	OK
 	DO
 	LOOP {
@@ -9460,6 +9479,7 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classPCA, 0, U"Align eigenvectors", nullptr, 1, DO_Eigens_alignEigenvectors);
 	praat_addAction1 (classPCA, 2, U"To Procrustes...", nullptr, 0, DO_PCAs_to_Procrustes);
 	praat_addAction1 (classPCA, 0, U"To TableOfReal (reconstruct 1)...", nullptr, 0, DO_PCA_to_TableOfReal_reconstruct1);
+	praat_addAction1 (classPCA, 0, U"Extract eigenvector...", nullptr, 0, DO_PCA_extractEigenvector);
 	praat_addAction1 (classPCA, 0, U"& TableOfReal: To Configuration?", nullptr, 0, DO_hint_PCA_and_TableOfReal_to_Configuration);
 	praat_addAction1 (classPCA, 0, U"& Configuration (reconstruct)?", nullptr, 0, DO_hint_PCA_and_Configuration_to_TableOfReal_reconstruct);
 	praat_addAction1 (classPCA, 0, U"& Covariance: Project?", nullptr, 0, DO_hint_PCA_and_Covariance_Project);
diff --git a/dwtools/praat_HMM_init.cpp b/dwtools/praat_HMM_init.cpp
index d71394c..43e335b 100644
--- a/dwtools/praat_HMM_init.cpp
+++ b/dwtools/praat_HMM_init.cpp
@@ -441,7 +441,7 @@ DO
 	LOOP {
 		iam (HMM);
 		double lnp = HMM_getProbabilityAtTimeBeingInState (me, itime, istate);
-		Melder_information (lnp, U" (=ln(p), p = ", Melder_naturalLogarithm (lnp), U") Being in state ",
+		Melder_information (lnp, U" (= ln(p), p = ", Melder_naturalLogarithm (lnp), U") Being in state ",
 			istate, U" at time ", itime);
 	}
 END
@@ -461,7 +461,7 @@ DO
 			itime,
 			istate,
 			isymbol);
-		Melder_information (lnp, U" (=ln(p), p = ", Melder_naturalLogarithm (lnp), U") Being in state ",
+		Melder_information (lnp, U" (= ln(p), p = ", Melder_naturalLogarithm (lnp), U") Being in state ",
 			istate, U" emitting symbol ", isymbol, U" at time ", itime);
 	}
 END
@@ -531,7 +531,7 @@ DO
 	}
 	Melder_assert (m1 && m2);
 	double ce = HMM_and_HMM_getCrossEntropy (m1, m2, n, sym);
-	Melder_information (ce, U" (=", (sym ? U"symmetric " : U""),
+	Melder_information (ce, U" (= ", (sym ? U"symmetric " : U""),
 		U" cross-entropy between models for observation length = ", n, U")");
 END
 
@@ -545,7 +545,7 @@ DIRECT (HMM_and_HMM_and_HMMObservationSequence_getCrossEntropy)
 	}
 	Melder_assert (m1 && m2 && hmm_os);
 	double ce = HMM_and_HMM_and_HMMObservationSequence_getCrossEntropy (m1, m2, hmm_os);
-	Melder_information (ce, U" (=symmetric cross-entropy between models)");
+	Melder_information (ce, U" (= symmetric cross-entropy between models)");
 END
 
 FORM (HMM_to_HMMObservationSequence, U"HMM: To HMMObservationSequence (generate observations)", U"HMM: To HMMObservationSequence...")
@@ -564,14 +564,14 @@ DIRECT (HMM_and_HMMStateSequence_getProbability)
 	HMM me = FIRST (HMM);
 	HMMStateSequence hmm_ss = FIRST (HMMStateSequence);
 	double lnp = HMM_and_HMMStateSequence_getProbability (me, hmm_ss);
-	Melder_information (lnp, U" (=ln(p), p = ", Melder_naturalLogarithm (lnp), U")");
+	Melder_information (lnp, U" (= ln(p), p = ", Melder_naturalLogarithm (lnp), U")");
 END
 
 DIRECT (HMM_and_HMMObservationSequence_getProbability)
 	HMM me = FIRST (HMM);
 	HMMObservationSequence hmm_os = FIRST (HMMObservationSequence);
 	double lnp = HMM_and_HMMObservationSequence_getProbability (me, hmm_os);
-	Melder_information (lnp, U" (=ln(p), p = ", Melder_naturalLogarithm (lnp), U")");
+	Melder_information (lnp, U" (= ln(p), p = ", Melder_naturalLogarithm (lnp), U")");
 END
 
 DIRECT (HMM_and_HMMObservationSequence_getCrossEntropy)
diff --git a/dwtools/praat_KlattGrid_init.cpp b/dwtools/praat_KlattGrid_init.cpp
index 92e983d..44f3a8c 100644
--- a/dwtools/praat_KlattGrid_init.cpp
+++ b/dwtools/praat_KlattGrid_init.cpp
@@ -398,7 +398,7 @@ END
 
 #define KlattGrid_ADD_FORMANT(Name,namef,formantType) \
 FORM (KlattGrid_add##Name##Formant, U"KlattGrid: Add " #namef "ormant", nullptr) \
-	INTEGER (U"Position", U"0 (=at end)") \
+	INTEGER (U"Position", U"0 (= at end)") \
 	OK \
 DO \
 	LOOP { iam (KlattGrid); \
@@ -409,7 +409,7 @@ END
 
 #define KlattGrid_REMOVE_FORMANT(Name,namef,formantType) \
 FORM (KlattGrid_remove##Name##Formant, U"KlattGrid: Remove " #namef "ormant", nullptr) \
-	INTEGER (U"Position", U"0 (=do nothing)") \
+	INTEGER (U"Position", U"0 (= do nothing)") \
 	OK \
 DO \
 	LOOP { iam (KlattGrid); \
@@ -420,7 +420,7 @@ END
 
 #define KlattGrid_ADD_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
 FORM (KlattGrid_add##Name##FormantFrequencyAndBandwidthTiers, U"KlattGrid: Add " #namef "ormant", nullptr) \
-	INTEGER (U"Position", U"0 (=at end)") \
+	INTEGER (U"Position", U"0 (= at end)") \
 	OK \
 DO \
 	LOOP { iam (KlattGrid); \
@@ -431,7 +431,7 @@ END
 
 #define KlattGrid_REMOVE_FORMANT_FREQUENCYANDBANDWIDTHTIERS(Name,namef,formantType) \
 FORM (KlattGrid_remove##Name##FormantFrequencyAndBandwidthTiers, U"KlattGrid: Remove " #namef "ormant", nullptr) \
-	INTEGER (U"Position", U"0 (=do nothing)") \
+	INTEGER (U"Position", U"0 (= do nothing)") \
 	OK \
 DO \
 	LOOP { iam (KlattGrid); \
@@ -442,7 +442,7 @@ END
 
 #define KlattGrid_ADD_FORMANT_AMPLITUDETIER(Name,namef,formantType) \
 FORM (KlattGrid_add##Name##FormantAmplitudeTier, U"KlattGrid: Add " #namef "ormant amplitude tier", nullptr) \
-	INTEGER (U"Position", U"0 (=at end)") \
+	INTEGER (U"Position", U"0 (= at end)") \
 	OK \
 DO \
 	LOOP { iam (KlattGrid); \
@@ -453,7 +453,7 @@ END
 
 #define KlattGrid_REMOVE_FORMANT_AMPLITUDETIER(Name,namef,formantType) \
 FORM (KlattGrid_remove##Name##FormantAmplitudeTier, U"KlattGrid: Remove " #namef "ormant amplitude tier", nullptr) \
-	INTEGER (U"Position", U"0 (=do nothing)") \
+	INTEGER (U"Position", U"0 (= do nothing)") \
 	OK \
 DO \
 	LOOP { iam (KlattGrid); \
@@ -741,7 +741,7 @@ KlattGrid_FORMANT_GET_ADD_REMOVE (Bandwidth, bandwidth, U" (Hz)", U"50.0", (valu
 
 FORM (KlattGrid_addFormantAndBandwidthTier, U"", nullptr)
 	KlattGrid_7formants_addCommonField (dia);
-	INTEGER (U"Position", U"0 (=at end)")
+	INTEGER (U"Position", U"0 (= at end)")
 	OK
 DO
 	long gridType = GET_INTEGER (U"Formant type");
diff --git a/dwtools/praat_MDS_init.cpp b/dwtools/praat_MDS_init.cpp
index a9e9b3f..03e1c77 100644
--- a/dwtools/praat_MDS_init.cpp
+++ b/dwtools/praat_MDS_init.cpp
@@ -624,7 +624,7 @@ DIRECT (Similarity_help)
 END
 
 FORM (Similarity_to_Dissimilarity, U"Similarity: To Dissimilarity", U"Similarity: To Dissimilarity...")
-	REAL (U"Maximum dissimilarity", U"0.0 (=from data)")
+	REAL (U"Maximum dissimilarity", U"0.0 (= from data)")
 	OK
 DO
 	LOOP {
@@ -1152,7 +1152,7 @@ END
 FORM (Dissimilarity_kruskal, U"Dissimilarity: To Configuration (kruskal)", U"Dissimilarity: To Configuration (kruskal)...")
 	LABEL (U"", U"Configuration")
 	NATURAL (U"Number of dimensions", U"2")
-	NATURAL (U"Distance metric", U"2 (=Euclidean)")
+	NATURAL (U"Distance metric", U"2 (= Euclidean)")
 	RADIO (U"Handling of ties", 1)
 	RADIOBUTTON (U"Primary approach")
 	RADIOBUTTON (U"Secondary approach")
diff --git a/fon/ExperimentMFC.cpp b/fon/ExperimentMFC.cpp
index 5626c12..d516cb9 100644
--- a/fon/ExperimentMFC.cpp
+++ b/fon/ExperimentMFC.cpp
@@ -39,6 +39,7 @@
  * pb 2011/03/23 C++
  * pb 2011/07/06 C++
  * pb 2013/01/01 added blank while playing; version 6
+ * pb 2016/09/25 added font size and response key to goodness ratings; version 7
  */
 
 #include "ExperimentMFC.h"
@@ -70,7 +71,7 @@
 
 #pragma mark - class ExperimentMFC
 
-Thing_implement (ExperimentMFC, Daata, 6);
+Thing_implement (ExperimentMFC, Daata, 7);
 
 static void readSound (ExperimentMFC me, const char32 *fileNameHead, const char32 *fileNameTail,
 	double medialSilenceDuration, char32 **name, autoSound *sound)
diff --git a/fon/ExperimentMFC_def.h b/fon/ExperimentMFC_def.h
index 4fbc6d6..f7aa767 100644
--- a/fon/ExperimentMFC_def.h
+++ b/fon/ExperimentMFC_def.h
@@ -1,6 +1,6 @@
 /* ExperimentMFC_def.h
  *
- * Copyright (C) 2001-2011,2013,2015 Paul Boersma
+ * Copyright (C) 2001-2011,2013,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
@@ -28,6 +28,7 @@
  * pb 2007/09/26 added font size (version 5)
  * pb 2011/03/03 added reaction time (version 2 of ResultsMFC)
  * pb 2013/01/01 added finalSilenceDuration and blankWhilePlaying (version 6)
+ * pb 2016/09/25 added font size and response key to goodness ratings (version 7)
  */
 
 /********* class ExperimentMFC **********/
@@ -102,6 +103,10 @@ oo_DEFINE_STRUCT (GoodnessMFC)
 	oo_FLOAT (bottom)
 	oo_FLOAT (top)
 	oo_STRING (label)
+	oo_FROM (7)
+		oo_INT (fontSize)
+		oo_STRING (key)
+	oo_ENDFROM
 
 oo_END_STRUCT (GoodnessMFC)
 #undef ooSTRUCT
diff --git a/fon/Photo.cpp b/fon/Photo.cpp
index 4f96f53..6b6115c 100644
--- a/fon/Photo.cpp
+++ b/fon/Photo.cpp
@@ -25,7 +25,7 @@
 	#include "macport_on.h"
 	#include <Cocoa/Cocoa.h>
 	#include "macport_off.h"
-#elif defined (linux)
+#elif defined (linux) && ! defined (NO_GRAPHICS)
 	#include <cairo/cairo.h>
 #endif
 
@@ -101,7 +101,7 @@ autoPhoto Photo_createSimple (long numberOfRows, long numberOfColumns) {
 
 autoPhoto Photo_readFromImageFile (MelderFile file) {
 	try {
-		#if defined (linux)
+		#if defined (linux) && ! defined (NO_GRAPHICS)
 			cairo_surface_t *surface = cairo_image_surface_create_from_png (Melder_peek32to8 (file -> path));
 			//if (cairo_surface_status)
 			//	Melder_throw (U"Error opening PNG file.");
@@ -221,7 +221,7 @@ autoPhoto Photo_readFromImageFile (MelderFile file) {
 	}
 #endif
 
-#ifdef linux
+#if defined (linux) && ! defined (NO_GRAPHICS)
 	static void _lin_saveAsImageFile (Photo me, MelderFile file, const char32 *which) {
 		cairo_format_t format = CAIRO_FORMAT_ARGB32;
 		long bytesPerRow = cairo_format_stride_for_width (format, my nx);   // likely to be my nx * 4
@@ -338,7 +338,7 @@ void Photo_saveAsPNG (Photo me, MelderFile file) {
 		_win_saveAsImageFile (me, file, U"image/png");
 	#elif defined (macintosh)
 		_mac_saveAsImageFile (me, file, kUTTypePNG);
-	#elif defined (linux)
+	#elif defined (linux) && ! defined (NO_GRAPHICS)
 		_lin_saveAsImageFile (me, file, U"image/png");
 	#endif
 }
diff --git a/fon/RunnerMFC.cpp b/fon/RunnerMFC.cpp
index 48b6b3e..f780e16 100644
--- a/fon/RunnerMFC.cpp
+++ b/fon/RunnerMFC.cpp
@@ -142,7 +142,9 @@ static void gui_drawingarea_cb_expose (RunnerMFC me, GuiDrawingArea_ExposeEvent
 			Graphics_fillRectangle (my graphics.get(), goodness -> left, goodness -> right, goodness -> bottom, goodness -> top);
 			Graphics_setColour (my graphics.get(), Graphics_MAROON);
 			Graphics_rectangle (my graphics.get(), goodness -> left, goodness -> right, goodness -> bottom, goodness -> top);
+			Graphics_setFontSize (my graphics.get(), goodness -> fontSize ? goodness -> fontSize : 24);
 			Graphics_text (my graphics.get(), 0.5 * (goodness -> left + goodness -> right), 0.5 * (goodness -> bottom + goodness -> top), goodness -> label);
+			Graphics_setFontSize (my graphics.get(), 24);
 		}
 		if (experiment -> replay_right > experiment -> replay_left && my numberOfReplays < experiment -> maximumNumberOfReplays) {
 			drawControlButton (me,
@@ -315,7 +317,6 @@ static void gui_drawingarea_cb_click (RunnerMFC me, GuiDrawingArea_ClickEvent ev
 			}
 		}
 	} else if (experiment -> trial <= experiment -> numberOfTrials) {
-		long iresponse;
 		if (x > experiment -> ok_left && x < experiment -> ok_right &&
 			y > experiment -> ok_bottom && y < experiment -> ok_top &&
 			experiment -> responses [experiment -> trial] != 0 &&
@@ -323,15 +324,18 @@ static void gui_drawingarea_cb_click (RunnerMFC me, GuiDrawingArea_ClickEvent ev
 		{
 			do_ok (me);
 		} else if (x > experiment -> replay_left && x < experiment -> replay_right &&
-			y > experiment -> replay_bottom && y < experiment -> replay_top && my numberOfReplays < experiment -> maximumNumberOfReplays)
+			y > experiment -> replay_bottom && y < experiment -> replay_top &&
+			my numberOfReplays < experiment -> maximumNumberOfReplays)
 		{
 			do_replay (me);
 		} else if (x > experiment -> oops_left && x < experiment -> oops_right &&
-			y > experiment -> oops_bottom && y < experiment -> oops_top && experiment -> trial > 1)
+			y > experiment -> oops_bottom && y < experiment -> oops_top)
 		{
-			do_oops (me);
+			if (experiment -> trial > 1) {
+				do_oops (me);
+			}
 		} else if (experiment -> responses [experiment -> trial] == 0 || experiment -> ok_right > experiment -> ok_left) {
-			for (iresponse = 1; iresponse <= experiment -> numberOfDifferentResponses; iresponse ++) {
+			for (long iresponse = 1; iresponse <= experiment -> numberOfDifferentResponses; iresponse ++) {
 				ResponseMFC response = & experiment -> response [iresponse];
 				if (x > response -> left && x < response -> right && y > response -> bottom && y < response -> top && response -> name [0] != '\0') {
 					experiment -> responses [experiment -> trial] = iresponse;
@@ -348,7 +352,7 @@ static void gui_drawingarea_cb_click (RunnerMFC me, GuiDrawingArea_ClickEvent ev
 				}
 			}
 			if (experiment -> responses [experiment -> trial] != 0 && experiment -> ok_right > experiment -> ok_left) {
-				for (iresponse = 1; iresponse <= experiment -> numberOfGoodnessCategories; iresponse ++) {
+				for (long iresponse = 1; iresponse <= experiment -> numberOfGoodnessCategories; iresponse ++) {
 					GoodnessMFC cat = & experiment -> goodness [iresponse];
 					if (x > cat -> left && x < cat -> right && y > cat -> bottom && y < cat -> top) {
 						experiment -> goodnesses [experiment -> trial] = iresponse;
@@ -359,7 +363,7 @@ static void gui_drawingarea_cb_click (RunnerMFC me, GuiDrawingArea_ClickEvent ev
 			}
 		} else if (experiment -> responses [experiment -> trial] != 0) {
 			Melder_assert (experiment -> ok_right <= experiment -> ok_left);
-			for (iresponse = 1; iresponse <= experiment -> numberOfGoodnessCategories; iresponse ++) {
+			for (long iresponse = 1; iresponse <= experiment -> numberOfGoodnessCategories; iresponse ++) {
 				GoodnessMFC cat = & experiment -> goodness [iresponse];
 				if (x > cat -> left && x < cat -> right && y > cat -> bottom && y < cat -> top) {
 					experiment -> goodnesses [experiment -> trial] = iresponse;
@@ -395,7 +399,6 @@ static void gui_drawingarea_cb_key (RunnerMFC me, GuiDrawingArea_KeyEvent event)
 	if (experiment -> trial == 0) {
 	} else if (experiment -> pausing) {
 	} else if (experiment -> trial <= experiment -> numberOfTrials) {
-		long iresponse;
 		if (experiment -> ok_key && experiment -> ok_key [0] == event -> key &&
 			experiment -> responses [experiment -> trial] != 0 &&
 			(experiment -> numberOfGoodnessCategories == 0 || experiment -> goodnesses [experiment -> trial] != 0))
@@ -409,8 +412,8 @@ static void gui_drawingarea_cb_key (RunnerMFC me, GuiDrawingArea_KeyEvent event)
 			if (experiment -> trial > 1) {
 				do_oops (me);
 			}
-		} else if (experiment -> responses [experiment -> trial] == 0) {
-			for (iresponse = 1; iresponse <= experiment -> numberOfDifferentResponses; iresponse ++) {
+		} else if (experiment -> responses [experiment -> trial] == 0 || experiment -> ok_right > experiment -> ok_left) {
+			for (long iresponse = 1; iresponse <= experiment -> numberOfDifferentResponses; iresponse ++) {
 				ResponseMFC response = & experiment -> response [iresponse];
 				if (response -> key && response -> key [0] == event -> key) {
 					experiment -> responses [experiment -> trial] = iresponse;
@@ -426,6 +429,25 @@ static void gui_drawingarea_cb_key (RunnerMFC me, GuiDrawingArea_KeyEvent event)
 					}
 				}
 			}
+			if (experiment -> responses [experiment -> trial] != 0 && experiment -> ok_right > experiment -> ok_left) {
+				for (long iresponse = 1; iresponse <= experiment -> numberOfGoodnessCategories; iresponse ++) {
+					GoodnessMFC cat = & experiment -> goodness [iresponse];
+					if (cat -> key && cat -> key [0] == event -> key) {
+						experiment -> goodnesses [experiment -> trial] = iresponse;
+						Editor_broadcastDataChanged (me);
+						Graphics_updateWs (my graphics.get());
+					}
+				}
+			}
+		} else if (experiment -> responses [experiment -> trial] != 0) {
+			Melder_assert (experiment -> ok_right <= experiment -> ok_left);
+			for (long iresponse = 1; iresponse <= experiment -> numberOfGoodnessCategories; iresponse ++) {
+				GoodnessMFC cat = & experiment -> goodness [iresponse];
+				if (cat -> key && cat -> key [0] == event -> key) {
+					experiment -> goodnesses [experiment -> trial] = iresponse;
+					do_ok (me);
+				}
+			}
 		}
 	}
 }
diff --git a/fon/manual_Exp.cpp b/fon/manual_Exp.cpp
index 47f9ee8..9c6b276 100644
--- a/fon/manual_Exp.cpp
+++ b/fon/manual_Exp.cpp
@@ -52,7 +52,7 @@ LIST_ITEM (U"@@ExperimentMFC 7. Blanking the screen")
 LIST_ITEM (U"@@ExperimentMFC 8. Running multiple experiments")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 1. When to use Praat", U"ppgb", 20130101)
+MAN_BEGIN (U"ExperimentMFC 1. When to use Praat", U"ppgb", 20160925)
 NORMAL (U"With Praat's ExperimentMFC, you can do simple experiments on identification and discrimination. "
 	"`Simple' means that for identification, the subject hears a sound and has to click on one of a set of "
 	"labelled rectangles (optionally, you can have the subject give a goodness-of-fit judgment). "
@@ -64,7 +64,7 @@ NORMAL (U"The advantage of using Praat's ExperimentMFC for this is that it is fr
 NORMAL (U"If you require more from your experiment design, you can use Praat's @@Demo window@; "
 	"with that less simple method you could for instance let the stimulus depend on the subject's previous responses. "
 	"Alternatively, you could use a dedicated program like Presentation or E-prime instead of Praat; "
-	"with these programs, you can also measure reaction times more accurately.")
+	"with these programs, you can also sometimes measure reaction times more accurately.")
 MAN_END
 
 MAN_BEGIN (U"ExperimentMFC 2. The first example", U"ppgb", 20051205)
@@ -81,14 +81,17 @@ LIST_ITEM (U"@@ExperimentMFC 2.8. Goodness judgments")
 LIST_ITEM (U"@@ExperimentMFC 2.9. How an experiment proceeds")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 2.1. The experiment file", U"ppgb", 20130101)
+MAN_BEGIN (U"ExperimentMFC 2.1. The experiment file", U"ppgb", 20160925)
 INTRO (U"An experiment is defined in a simple text file, which we call an %%experiment file%. "
 	"The following is an example of such an experiment file. The first two lines have to be typed "
 	"exactly as in this example, the rest depends on your stimuli, on your response categories, "
 	"and on the way the experiment is to be presented to the listener. "
-	"The order of the elements in this file cannot be changed, and nothing can be skipped.")
+	"The order of the elements in this file cannot be changed, and nothing can be skipped. "
+	"The first two lines make the file recognizable for Praat as an ExperimentMFC file; "
+	"the number 7 is the version number of this type of ExperimentMFC file "
+	"(Praat can still read older ExperimentMFC files with a lower version number).")
 CODE (U"\"ooTextFile\"")
-CODE (U"\"ExperimentMFC 6\"")
+CODE (U"\"ExperimentMFC 7\"")
 CODE (U"blankWhilePlaying? <no>")
 CODE (U"stimuliAreSounds? <yes>")
 CODE (U"stimulusFileNameHead = \"Sounds/\"")
@@ -125,11 +128,11 @@ CODE1 (U"0.4 0.5 0.3 0.4 \"h A d\" 40 \"\" \"a\"")
 CODE1 (U"0.5 0.6 0.5 0.6 \"h O d\" 40 \"\" \"o\"")
 CODE1 (U"0.6 0.7 0.7 0.8 \"h U d\" 40 \"\" \"u\"")
 CODE (U"numberOfGoodnessCategories = 5")
-CODE1 (U"0.25 0.35 0.10 0.20 \"1 (poor)\"")
-CODE1 (U"0.35 0.45 0.10 0.20 \"2\"")
-CODE1 (U"0.45 0.55 0.10 0.20 \"3\"")
-CODE1 (U"0.55 0.65 0.10 0.20 \"4\"")
-CODE1 (U"0.65 0.75 0.10 0.20 \"5 (good)\"")
+CODE1 (U"0.25 0.35 0.10 0.20 \"1 (poor)\" 24 \"\"")
+CODE1 (U"0.35 0.45 0.10 0.20 \"2\" 24 \"\"")
+CODE1 (U"0.45 0.55 0.10 0.20 \"3\" 24 \"\"")
+CODE1 (U"0.55 0.65 0.10 0.20 \"4\" 24 \"\"")
+CODE1 (U"0.65 0.75 0.10 0.20 \"5 (good)\" 24 \"\"")
 NORMAL (U"This experiment will play 4 different stimuli to the listener, each 3 times. "
 	"Thus, the listener is confronted with 12 trials.")
 MAN_END
@@ -269,11 +272,12 @@ NORMAL (U"In this example, the picture ##hello.jpg# from the subdirectory #pictu
 	"This currently (September 2011) works only on the Mac and Windows.")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 2.8. Goodness judgments", U"ppgb", 20051205)
+MAN_BEGIN (U"ExperimentMFC 2.8. Goodness judgments", U"ppgb", 20160925)
 NORMAL (U"If %numberOfGoodnessCategories is not 0, some more rectangles will be drawn, "
 	"as in the @@ExperimentMFC 2.1. The experiment file|example experiment at . "
 	"You specify again the locations of these rectangles (in the example, they touch each other), "
-	"and the texts on them. Praat will record the number of the button when the listener clicks on it. "
+	"the texts on them, the font sizes, and the response keys. "
+	"Praat will record the number of the button when the listener clicks on it. "
 	"Thus, if she clicks on the button labelled \"1 (poor)\", Praat will record a goodness judgment of 1, "
 	"because this is the first button in the list. If she clicks on \"5 (good)\", Praat will record a "
 	"goodness judgment of 5.")
@@ -305,7 +309,7 @@ LIST_ITEM (U"@@ExperimentMFC 3.3. A 4I-oddity experiment")
 LIST_ITEM (U"@@ExperimentMFC 3.4. Variable inter-stimulus intervals")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 3.1. A simple discrimination experiment", U"ppgb", 20130416)
+MAN_BEGIN (U"ExperimentMFC 3.1. A simple discrimination experiment", U"ppgb", 20160925)
 NORMAL (U"The @@ExperimentMFC 2.1. The experiment file|example experiment@ was an %identification experiment: "
 	"the subject had identify a single sound as one element of a set of categories. "
 	"Phoneticians will often do %discrimination experiments, which are experiments in which "
@@ -320,7 +324,7 @@ NORMAL (U"The simplest discrimination task has only two sub-stimuli, and the sub
 	"will always be heard as different, you do not include pairs in which the distance is larger than 60 Hz. "
 	"The experiment file will look like this:")
 CODE (U"\"ooTextFile\"")
-CODE (U"\"ExperimentMFC 6\"")
+CODE (U"\"ExperimentMFC 7\"")
 CODE (U"blank while playing? <no>")
 CODE (U"stimuli are sounds? <yes>")
 CODE (U"\"stimuli/\"  \".wav\"")
@@ -364,12 +368,12 @@ NORMAL (U"Note that the text in this file is rather different from the previous
 	"Praat ignores these texts as long as they do not contain numbers, quoted strings, or things between <>.")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 3.2. An AXB discrimination experiment", U"ppgb", 20130101)
+MAN_BEGIN (U"ExperimentMFC 3.2. An AXB discrimination experiment", U"ppgb", 20160925)
 INTRO (U"In the AXB task, the subject will hear three stimuli in sequence, and has to say "
 	"whether the second (X) is more similar to the first (A) or to the second (B). "
 	"An experiment file could look like follows:")
 CODE (U"\"ooTextFile\"")
-CODE (U"\"ExperimentMFC 6\"")
+CODE (U"\"ExperimentMFC 7\"")
 CODE (U"blankWhilePlaying? <no>")
 CODE (U"stimuliAreSounds? <yes>")
 CODE (U"\"stimuli/\"  \".wav\"")
@@ -405,12 +409,12 @@ NORMAL (U"In this example, the subject has to click 400 times. She sees three bu
 	"In your ResultsMFC object, you will only see %A and %B responses.")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 3.3. A 4I-oddity experiment", U"ppgb", 20130101)
+MAN_BEGIN (U"ExperimentMFC 3.3. A 4I-oddity experiment", U"ppgb", 20160925)
 NORMAL (U"In the four-items-oddity task, the subject will hear four stimuli in sequence, and has to say "
 	"whether the second or the third is the odd one out. The other three substimuli are identical. "
 	"An experiment file could look as follows:")
 CODE (U"\"ooTextFile\"")
-CODE (U"\"ExperimentMFC 6\"")
+CODE (U"\"ExperimentMFC 7\"")
 CODE (U"blankWhilePlaying? <no>")
 CODE (U"stimuliAreSounds? <yes>")
 CODE (U"stimulusFileNameHead = \"stimuli/\"")
@@ -638,7 +642,7 @@ CODE1 (U"0.2 0.4 0.7 0.8 \"\" 40 \"\" \"left\"")
 CODE1 (U"0.6 0.8 0.7 0.8 \"\" 40 \"\" \"right\"")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 6. Responses are sounds", U"ppgb", 20130101)
+MAN_BEGIN (U"ExperimentMFC 6. Responses are sounds", U"ppgb", 20160925)
 INTRO (U"In the @@ExperimentMFC 2.1. The experiment file|example experiment@, "
 	"the stimuli were sounds, and the responses were categories whose labels appeared on buttons. "
 	"Sometimes you want it the other way around.")
@@ -651,7 +655,7 @@ NORMAL (U"Such a task can be regarded as reversing the task of the example exper
 	"In the /i/ prototype task, the stimulus is a phonological category, and the response is a sound.")
 NORMAL (U"This is what the experiment file could look like:")
 CODE (U"\"ooTextFile\"")
-CODE (U"\"ExperimentMFC 6\"")
+CODE (U"\"ExperimentMFC 7\"")
 CODE (U"blankWhilePlaying? <no>")
 CODE (U"stimuliAreSounds? <no> \"\" \"\" \"\" \"\" 0 0 0")
 CODE (U"numberOfDifferentStimuli = 2")
@@ -694,11 +698,11 @@ CODE1 (U"0.3 0.4 0.4 0.5 \"\" 10 \"\" \"i42\"")
 CODE1 (U"0.4 0.5 0.4 0.5 \"\" 10 \"\" \"i43\"")
 CODE1 (U"0.5 0.6 0.4 0.5 \"\" 10 \"\" \"i44\"")
 CODE (U"numberOfGoodnessCategories = 5")
-CODE1 (U"0.25 0.35 0.10 0.20 \"1 (poor)\"")
-CODE1 (U"0.35 0.45 0.10 0.20 \"2\"")
-CODE1 (U"0.45 0.55 0.10 0.20 \"3\"")
-CODE1 (U"0.55 0.65 0.10 0.20 \"4\"")
-CODE1 (U"0.65 0.75 0.10 0.20 \"5 (good)\"")
+CODE1 (U"0.25 0.35 0.10 0.20 \"1 (poor)\" 24 \"\"")
+CODE1 (U"0.35 0.45 0.10 0.20 \"2\" 24 \"\"")
+CODE1 (U"0.45 0.55 0.10 0.20 \"3\" 24 \"\"")
+CODE1 (U"0.55 0.65 0.10 0.20 \"4\" 24 \"\"")
+CODE1 (U"0.65 0.75 0.10 0.20 \"5 (good)\" 24 \"\"")
 NORMAL (U"The participant will see 16 squares on the screen. First she will have to find the best /i/, "
 	"then the best /\\ic/. The sound files ##Sounds/i11.wav# and so on must exist and have the same sampling frequency. "
 	"A silence of 0.3 seconds is played just before each response sound.")
@@ -716,7 +720,7 @@ NORMAL (U"If you want the response buttons to come up 0.5 seconds after the soun
 	"you set the %stimulusFinalSilenceDuration to 0.5.")
 MAN_END
 
-MAN_BEGIN (U"ExperimentMFC 8. Running multiple experiments", U"ppgb", 20130101)
+MAN_BEGIN (U"ExperimentMFC 8. Running multiple experiments", U"ppgb", 20160925)
 INTRO (U"In all the earlier examples, either the set of stimulus sounds or the set of response sounds stayed "
 	"the same throughout the experiment. If you want more than one set of stimuli, or more than one set of responses, "
 	"you can run several experiments after each other, simply by selecting more than one experiment, then clicking #Run.")
@@ -725,7 +729,7 @@ NORMAL (U"You can put all these ExperimentMFC objects in one text file. The foll
 CODE (U"\"ooTextFile\"")
 CODE (U"\"Collection\" 2")
 CODE (U"")
-CODE (U"\"ExperimentMFC 6\" \"i\"")
+CODE (U"\"ExperimentMFC 7\" \"i\"")
 CODE (U"blankWhilePlaying? <no>")
 CODE (U"stimuliAreSounds? <no> \"\" \"\" \"\" \"\" 0 0 0")
 CODE (U"numberOfDifferentStimuli = 1")
@@ -758,7 +762,7 @@ CODE1 (U"0.6 0.7 0.7 0.8 \"\" 10 \"\" \"i5\"")
 CODE1 (U"0.7 0.8 0.7 0.8 \"\" 10 \"\" \"i6\"")
 CODE (U"numberOfGoodnessCategories = 0")
 CODE (U"")
-CODE (U"\"ExperimentMFC 6\" \"u\"")
+CODE (U"\"ExperimentMFC 7\" \"u\"")
 CODE (U"blankWhilePlaying? <no>")
 CODE (U"stimuliAreSounds? <no> \"\" \"\" \"\" \"\" 0 0 0")
 CODE (U"numberOfDifferentStimuli = 1")
diff --git a/fon/manual_tutorials.cpp b/fon/manual_tutorials.cpp
index 5bbd1ed..65c7361 100644
--- a/fon/manual_tutorials.cpp
+++ b/fon/manual_tutorials.cpp
@@ -22,10 +22,18 @@
 void manual_tutorials_init (ManPages me);
 void manual_tutorials_init (ManPages me) {
 
-MAN_BEGIN (U"What's new?", U"ppgb", 20160613)
+MAN_BEGIN (U"What's new?", U"ppgb", 20160925)
 INTRO (U"Latest changes in Praat.")
 //LIST_ITEM (U"• Manual page about @@drawing a vowel triangle at .")
 
+NORMAL (U"##6.0.21# (25 September 2016)")
+LIST_ITEM (U"• ExperimentMFC: you can now specify font sizes and response keys for goodness judgments.")
+LIST_ITEM (U"• Table: when drawing ellipses, a font size of 0 can now be used to prevent drawing the labels.")
+LIST_ITEM (U"• Mac: dragging selections repaired for System 10.11.6 (but we advise to upgrade to 10.12).")
+LIST_ITEM (U"• Mac: re-enabled Return key for confirming some dialog boxes.")
+NORMAL (U"##6.0.20# (3 September 2016)")
+LIST_ITEM (U"• Can open UTF-8 text files with Byte Order Mark.")
+LIST_ITEM (U"• Scripting improvement: function names can now be used as names of indexed variables.")
 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)")
@@ -2737,7 +2745,7 @@ NORMAL (U"Normally, the range of pitch values that can be seen in the editor win
 	"You will usually do this by changing the pitch range in the @@Pitch settings...@ window. "
 	"However, the analysis range will also change in that case, so that the curve itself may change. "
 	"If you do not want that, you can change the %%View range% settings "
-	"from \"0.0 (= auto)\" - \"0.0 (=auto)\" to something else, perhaps \"350\" - \"400\".")
+	"from \"0.0 (= auto)\" - \"0.0 (= auto)\" to something else, perhaps \"350\" - \"400\".")
 ENTRY (U"Pitch analysis settings")
 NORMAL (U"For information about these, see @@Sound: To Pitch (ac)... at . The standard settings are best in most cases. "
 	"For some pathological voices, you will want to set the voicing threshold to much less than the standard of 0.45, "
diff --git a/fon/praat_Fon.cpp b/fon/praat_Fon.cpp
index 39ef9fc..ee707ba 100644
--- a/fon/praat_Fon.cpp
+++ b/fon/praat_Fon.cpp
@@ -5962,7 +5962,7 @@ END2 }
 FORM (Strings_replaceAll, U"Strings: Replace all", nullptr) {
 	SENTENCE (U"Find", U"a")
 	SENTENCE (U"Replace with", U"b")
-	INTEGER (U"Replace limit per string", U"0 (=unlimited)")
+	INTEGER (U"Replace limit per string", U"0 (= unlimited)")
 	RADIO (U"Find and replace strings are", 1)
 		RADIOBUTTON (U"literals")
 		RADIOBUTTON (U"regular expressions")
diff --git a/makefiles/makefile.defs.linux.barren b/makefiles/makefile.defs.linux.barren
new file mode 100644
index 0000000..8f3a2b1
--- /dev/null
+++ b/makefiles/makefile.defs.linux.barren
@@ -0,0 +1,23 @@
+# File: makefile.defs.linux.barren
+
+# System: Linux without GUI, network, graphics, and sound
+# Paul Boersma, 12 September 2016
+
+CC = gcc -std=gnu99
+
+CXX = g++ -std=c++11
+
+CFLAGS = -DNO_GRAPHICS -DNO_NETWORK -D_FILE_OFFSET_BITS=64 -DUNIX -Dlinux -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O3 -g1 -pthread
+
+CXXFLAGS = $(CFLAGS) -Wshadow
+
+LINK = g++
+
+EXECUTABLE = praat
+
+LIBS = -lm -lpthread -static -static-libgcc -static-libstdc++
+
+AR = ar
+RANLIB = ls
+ICON =
+MAIN_ICON =
diff --git a/makefiles/makefile.defs.linux.silent b/makefiles/makefile.defs.linux.silent
index 6182c49..235e06f 100644
--- a/makefiles/makefile.defs.linux.silent
+++ b/makefiles/makefile.defs.linux.silent
@@ -1,7 +1,7 @@
 # File: makefile.defs.linux.silent
 
 # System: Linux without sound
-# Paul Boersma, 22 March 2014
+# Paul Boersma, 10 September 2016
 
 CC = gcc -std=gnu99
 
@@ -15,7 +15,7 @@ LINK = g++
 
 EXECUTABLE = praat
 
-LIBS = `pkg-config --libs gtk+-2.0` -lm -lpthread
+LIBS = `pkg-config --libs gtk+-2.0` -lm -lpthread -static-libgcc -static-libstdc++
 
 AR = ar
 RANLIB = ls
diff --git a/num/NUM.h b/num/NUM.h
index 21d7cc1..c73388d 100644
--- a/num/NUM.h
+++ b/num/NUM.h
@@ -333,6 +333,7 @@ double NUMrandomUniform (double lowest, double highest);
 long NUMrandomInteger (long lowest, long highest);
 
 bool NUMrandomBernoulli (double probability);
+double NUMrandomBernoulli_real (double probability);
 
 double NUMrandomGauss (double mean, double standardDeviation);
 double NUMrandomGauss_mt (int threadNumber, double mean, double standardDeviation);
@@ -503,7 +504,14 @@ void NUMmatrix_free (T** ptr, long row1, long col1) {
 
 template <class T>
 T** NUMmatrix_copy (T** ptr, long row1, long row2, long col1, long col2) {
+	#if 1
 	T** result = static_cast <T**> (NUMmatrix_copy (sizeof (T), ptr, row1, row2, col1, col2));
+	#else
+	T** result = static_cast <T**> (NUMmatrix (sizeof (T), row1, row2, col1, col2));
+	for (long irow = row1; irow <= row2; irow ++)
+		for (long icol = col1; icol <= col2; icol ++)
+			result [irow] [icol] = ptr [irow] [icol];
+	#endif
 	return result;
 }
 
diff --git a/num/NUMrandom.cpp b/num/NUMrandom.cpp
index 850ac95..5c48cbf 100644
--- a/num/NUMrandom.cpp
+++ b/num/NUMrandom.cpp
@@ -275,6 +275,10 @@ bool NUMrandomBernoulli (double probability) {
 	return NUMrandomFraction() < probability;
 }
 
+double NUMrandomBernoulli_real (double probability) {
+	return (double) (NUMrandomFraction() < probability);
+}
+
 #define repeat  do
 #define until(cond)  while (! (cond))
 double NUMrandomGauss (double mean, double standardDeviation) {
diff --git a/sys/Collection.h b/sys/Collection.h
index d56067c..9e6a31b 100644
--- a/sys/Collection.h
+++ b/sys/Collection.h
@@ -270,8 +270,8 @@ struct CollectionOf : structDaata {
 				for (long j = i; j < our size; j ++) {
 					our at [j] = our at [j + 1];
 				}
+				our size --;
 			}
-			our size --;
 		}
 	}
 
diff --git a/sys/Formula.cpp b/sys/Formula.cpp
index d6a4a94..e84b8ee 100644
--- a/sys/Formula.cpp
+++ b/sys/Formula.cpp
@@ -95,13 +95,20 @@ enum { GEENSYMBOOL_,
 
 	/* Functions of 1 variable; if you add, update the #defines. */
 	#define LOW_FUNCTION_1  ABS_
-		ABS_, ROUND_, FLOOR_, CEILING_, SQRT_, SIN_, COS_, TAN_, ARCSIN_, ARCCOS_, ARCTAN_, SINC_, SINCPI_,
-		EXP_, SINH_, COSH_, TANH_, ARCSINH_, ARCCOSH_, ARCTANH_,
-		SIGMOID_, INV_SIGMOID_, ERF_, ERFC_, GAUSS_P_, GAUSS_Q_, INV_GAUSS_Q_,
-		RANDOM_POISSON_, LOG2_, LN_, LOG10_, LN_GAMMA_,
+		ABS_, ROUND_, FLOOR_, CEILING_,
+		RECTIFY_, RECTIFY_NUMVEC_,
+		SQRT_, SIN_, COS_, TAN_, ARCSIN_, ARCCOS_, ARCTAN_, SINC_, SINCPI_,
+		EXP_, EXP_NUMVEC_, EXP_NUMMAT_,
+		SINH_, COSH_, TANH_, ARCSINH_, ARCCOSH_, ARCTANH_,
+		SIGMOID_, SIGMOID_NUMVEC_, SOFTMAX_NUMVEC_,
+		INV_SIGMOID_, ERF_, ERFC_, GAUSS_P_, GAUSS_Q_, INV_GAUSS_Q_,
+		RANDOM_BERNOULLI_, RANDOM_BERNOULLI_NUMVEC_,
+		RANDOM_POISSON_,
+		LOG2_, LN_, LOG10_, LN_GAMMA_,
 		HERTZ_TO_BARK_, BARK_TO_HERTZ_, PHON_TO_DIFFERENCE_LIMENS_, DIFFERENCE_LIMENS_TO_PHON_,
 		HERTZ_TO_MEL_, MEL_TO_HERTZ_, HERTZ_TO_SEMITONES_, SEMITONES_TO_HERTZ_,
 		ERB_, HERTZ_TO_ERB_, ERB_TO_HERTZ_,
+		SUM_, MEAN_, STDEV_, CENTER_,
 		STRINGSTR_,
 	#define HIGH_FUNCTION_1  STRINGSTR_
 
@@ -112,7 +119,8 @@ enum { GEENSYMBOOL_,
 		INV_CHI_SQUARE_Q_, STUDENT_P_, STUDENT_Q_, INV_STUDENT_Q_,
 		BETA_, BETA2_, BESSEL_I_, BESSEL_K_, LN_BETA_,
 		SOUND_PRESSURE_TO_PHON_, OBJECTS_ARE_IDENTICAL_,
-	#define HIGH_FUNCTION_2  OBJECTS_ARE_IDENTICAL_
+		OUTER_NUMMAT_, MUL_NUMVEC_,
+	#define HIGH_FUNCTION_2  MUL_NUMVEC_
 
 	/* Functions of 3 variables; if you add, update the #defines. */
 	#define LOW_FUNCTION_3  FISHER_P_
@@ -159,9 +167,9 @@ enum { GEENSYMBOOL_,
 	#define HIGH_STRING_FUNCTION  PERCENTSTR_
 
 	/* Range functions. */
-	#define LOW_RANGE_FUNCTION  SUM_
-		SUM_,
-	#define HIGH_RANGE_FUNCTION  SUM_
+	#define LOW_RANGE_FUNCTION  SUM_OVER_
+		SUM_OVER_,
+	#define HIGH_RANGE_FUNCTION  SUM_OVER_
 
 	#define LOW_FUNCTION  LOW_FUNCTION_1
 	#define HIGH_FUNCTION  HIGH_RANGE_FUNCTION
@@ -209,18 +217,26 @@ static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
 	U"row", U"col", U"nrow", U"ncol", U"row$", U"col$", U"y", U"x",
 	U"self", U"self$", U"object", U"object$", U"_matriks", U"_matriks$",
 	U"stopwatch",
-	U"abs", U"round", U"floor", U"ceiling", U"sqrt", U"sin", U"cos", U"tan", U"arcsin", U"arccos", U"arctan", U"sinc", U"sincpi",
-	U"exp", U"sinh", U"cosh", U"tanh", U"arcsinh", U"arccosh", U"arctanh",
-	U"sigmoid", U"invSigmoid", U"erf", U"erfc", U"gaussP", U"gaussQ", U"invGaussQ",
-	U"randomPoisson", U"log2", U"ln", U"log10", U"lnGamma",
+	U"abs", U"round", U"floor", U"ceiling",
+	U"rectify", U"rectify#",
+	U"sqrt", U"sin", U"cos", U"tan", U"arcsin", U"arccos", U"arctan", U"sinc", U"sincpi",
+	U"exp", U"exp#", U"exp##",
+	U"sinh", U"cosh", U"tanh", U"arcsinh", U"arccosh", U"arctanh",
+	U"sigmoid", U"sigmoid#", U"softmax#",
+	U"invSigmoid", U"erf", U"erfc", U"gaussP", U"gaussQ", U"invGaussQ",
+	U"randomBernoulli", U"randomBernoulli#",
+	U"randomPoisson",
+	U"log2", U"ln", U"log10", U"lnGamma",
 	U"hertzToBark", U"barkToHertz", U"phonToDifferenceLimens", U"differenceLimensToPhon",
 	U"hertzToMel", U"melToHertz", U"hertzToSemitones", U"semitonesToHertz",
 	U"erb", U"hertzToErb", U"erbToHertz",
+	U"sum", U"mean", U"stdev", U"center",
 	U"string$",
 	U"arctan2", U"randomUniform", U"randomInteger", U"randomGauss", U"randomBinomial",
 	U"chiSquareP", U"chiSquareQ", U"incompleteGammaP", U"invChiSquareQ", U"studentP", U"studentQ", U"invStudentQ",
 	U"beta", U"beta2", U"besselI", U"besselK", U"lnBeta",
 	U"soundPressureToPhon", U"objectsAreIdentical",
+	U"outer##", U"mul#",
 	U"fisherP", U"fisherQ", U"invFisherQ",
 	U"binomialP", U"binomialQ", U"incompleteBeta", U"invBinomialP", U"invBinomialQ",
 
@@ -253,7 +269,7 @@ static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
 	U"startsWith", U"endsWith", U"replace$", U"index_regex", U"rindex_regex", U"replace_regex$",
 	U"extractNumber", U"extractWord$", U"extractLine$",
 	U"fixed$", U"percent$",
-	U"sum",
+	U"sumOver",
 	U".",
 	U"_true", U"_false",
 	U"_goto", U"_iftrue", U"_iffalse", U"_incrementGreaterGoto",
@@ -438,9 +454,17 @@ static void Formula_lexan () {
 					 */
 					int jkar;
 					jkar = ikar + 1;
-					while (theExpression [jkar] == ' ' || theExpression [jkar] == '\t') jkar ++;
-					if (theExpression [jkar] == '(' || theExpression [jkar] == ':') {
+					while (theExpression [jkar] == U' ' || theExpression [jkar] == U'\t') jkar ++;
+					if (theExpression [jkar] == U'(' || theExpression [jkar] == U':') {
 						nieuwtok (tok)   // this must be a function name
+					} else if (theExpression [jkar] == U'[' && rank == 0) {
+						if (isString) {
+							nieuwtok (INDEXED_STRING_VARIABLE_)
+						} else {
+							nieuwtok (INDEXED_NUMERIC_VARIABLE_)
+						}
+						lexan [itok]. content.string = Melder_dup_f (token.string);
+						numberOfStringConstants ++;
 					} else {
 						/*
 						 * This could be a variable with the same name as a function.
@@ -1377,7 +1401,7 @@ static void parsePowerFactor () {
 	}
 
 	if (symbol >= LOW_RANGE_FUNCTION && symbol <= HIGH_RANGE_FUNCTION) {
-		if (symbol == SUM_) {
+		if (symbol == SUM_OVER_) {
 			//theOptimize = 1;
 			nieuwontleed (NUMBER_); parsenumber (0.0);   // initialize the sum
             bool isParenthesis = pasArguments ();
@@ -2092,10 +2116,84 @@ static void do_gt () {
 	}
 }
 static void do_add () {
+	/*
+		result.. = x.. + y..
+	*/
 	Stackel y = pop, x = pop;
-	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		pushNumber (x->number == NUMundefined || y->number == NUMundefined ? NUMundefined :
-			x->number + y->number);
+	if (x->which == Stackel_NUMBER) {
+		double xvalue = x->number;
+		if (y->which == Stackel_NUMBER) {
+			/*
+				result = x + y
+			*/
+			double yvalue = y->number;
+			pushNumber (xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue + yvalue);
+		} else if (y->which == Stackel_NUMERIC_VECTOR) {
+			/*
+				result# = x + y#
+			*/
+			long ny = y->numericVector.numberOfElements;
+			double *result = NUMvector<double> (1, ny);
+			if (xvalue == NUMundefined) {
+				for (long i = 1; i <= ny; i ++) {
+					result [i] = NUMundefined;
+				}
+			} else {
+				for (long i = 1; i <= ny; i ++) {
+					double yvalue = y->numericVector.data [i];
+					result [i] = yvalue == NUMundefined ? NUMundefined : xvalue + yvalue;
+				}
+			}
+			pushNumericVector (ny, result);
+		} else if (y->which == Stackel_NUMERIC_MATRIX) {
+			/*
+				result## = x + y##
+			*/
+			long nrow = y->numericMatrix.numberOfRows, ncol = y->numericMatrix.numberOfColumns;
+			double **result = NUMmatrix<double> (1, nrow, 1, ncol);
+			if (xvalue == NUMundefined) {
+				for (long irow = 1; irow <= nrow; irow ++) {
+					for (long icol = 1; icol <= ncol; icol ++) {
+						result [irow] [icol] = NUMundefined;
+					}
+				}
+			} else {
+				for (long irow = 1; irow <= nrow; irow ++) {
+					for (long icol = 1; icol <= ncol; icol ++) {
+						double yvalue = y->numericMatrix.data [irow] [icol];
+						result [irow] [icol] = yvalue == NUMundefined ? NUMundefined : xvalue + yvalue;
+					}
+				}
+			}
+			pushNumericMatrix (nrow, ncol, result);
+		}
+	} else if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_VECTOR) {
+		long nx = x->numericVector.numberOfElements, ny = y->numericVector.numberOfElements;
+		if (nx != ny)
+			Melder_throw (U"When adding vectors, their numbers of elements should be equal, instead of ", nx, U" and ", ny, U".");
+		double *result = NUMvector<double> (1, nx);
+		for (long i = 1; i <= nx; i ++) {
+			double xvalue = x->numericVector.data [i];
+			double yvalue = y->numericVector.data [i];
+			result [i] = xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue + yvalue;
+		}
+		pushNumericVector (nx, result);
+	} else if (x->which == Stackel_NUMERIC_MATRIX && y->which == Stackel_NUMERIC_MATRIX) {
+		long xnrow = x->numericMatrix.numberOfRows, xncol = x->numericMatrix.numberOfColumns;
+		long ynrow = y->numericMatrix.numberOfRows, yncol = y->numericMatrix.numberOfColumns;
+		if (xnrow != ynrow)
+			Melder_throw (U"When adding matrices, their numbers of rows should be equal, instead of ", xnrow, U" and ", ynrow, U".");
+		if (xncol != yncol)
+			Melder_throw (U"When adding matrices, their numbers of columns should be equal, instead of ", xncol, U" and ", yncol, U".");
+		double **result = NUMmatrix<double> (1, xnrow, 1, xncol);
+		for (long irow = 1; irow <= xnrow; irow ++) {
+			for (long icol = 1; icol <= xncol; icol ++) {
+				double xvalue = x->numericMatrix.data [irow] [icol];
+				double yvalue = y->numericMatrix.data [irow] [icol];
+				result [irow] [icol] = xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue + yvalue;
+			}
+		}
+		pushNumericMatrix (xnrow, xncol, result);
 	} else if (x->which == Stackel_STRING && y->which == Stackel_STRING) {
 		long length1 = str32len (x->string), length2 = str32len (y->string);
 		char32 *result = Melder_malloc (char32, length1 + length2 + 1);
@@ -2107,10 +2205,84 @@ static void do_add () {
 	}
 }
 static void do_sub () {
+	/*
+		result.. = x.. - y..
+	*/
 	Stackel y = pop, x = pop;
-	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		pushNumber (x->number == NUMundefined || y->number == NUMundefined ? NUMundefined :
-			x->number - y->number);
+	if (x->which == Stackel_NUMBER) {
+		double xvalue = x->number;
+		if (y->which == Stackel_NUMBER) {
+			/*
+				result = x - y
+			*/
+			double yvalue = y->number;
+			pushNumber (xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue - yvalue);
+		} else if (y->which == Stackel_NUMERIC_VECTOR) {
+			/*
+				result# = x - y#
+			*/
+			long ny = y->numericVector.numberOfElements;
+			double *result = NUMvector<double> (1, ny);
+			if (xvalue == NUMundefined) {
+				for (long i = 1; i <= ny; i ++) {
+					result [i] = NUMundefined;
+				}
+			} else {
+				for (long i = 1; i <= ny; i ++) {
+					double yvalue = y->numericVector.data [i];
+					result [i] = yvalue == NUMundefined ? NUMundefined : xvalue - yvalue;
+				}
+			}
+			pushNumericVector (ny, result);
+		} else if (y->which == Stackel_NUMERIC_MATRIX) {
+			/*
+				result## = x - y##
+			*/
+			long nrow = y->numericMatrix.numberOfRows, ncol = y->numericMatrix.numberOfColumns;
+			double **result = NUMmatrix<double> (1, nrow, 1, ncol);
+			if (xvalue == NUMundefined) {
+				for (long irow = 1; irow <= nrow; irow ++) {
+					for (long icol = 1; icol <= ncol; icol ++) {
+						result [irow] [icol] = NUMundefined;
+					}
+				}
+			} else {
+				for (long irow = 1; irow <= nrow; irow ++) {
+					for (long icol = 1; icol <= ncol; icol ++) {
+						double yvalue = y->numericMatrix.data [irow] [icol];
+						result [irow] [icol] = yvalue == NUMundefined ? NUMundefined : xvalue - yvalue;
+					}
+				}
+			}
+			pushNumericMatrix (nrow, ncol, result);
+		}
+	} else if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_VECTOR) {
+		long nx = x->numericVector.numberOfElements, ny = y->numericVector.numberOfElements;
+		if (nx != ny)
+			Melder_throw (U"When subtracting vectors, their numbers of elements should be equal, instead of ", nx, U" and ", ny, U".");
+		double *result = NUMvector<double> (1, nx);
+		for (long i = 1; i <= nx; i ++) {
+			double xvalue = x->numericVector.data [i];
+			double yvalue = y->numericVector.data [i];
+			result [i] = xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue - yvalue;
+		}
+		pushNumericVector (nx, result);
+	} else if (x->which == Stackel_NUMERIC_MATRIX && y->which == Stackel_NUMERIC_MATRIX) {
+		long xnrow = x->numericMatrix.numberOfRows, xncol = x->numericMatrix.numberOfColumns;
+		long ynrow = y->numericMatrix.numberOfRows, yncol = y->numericMatrix.numberOfColumns;
+		if (xnrow != ynrow)
+			Melder_throw (U"When subtracting matrices, their numbers of rows should be equal, instead of ", xnrow, U" and ", ynrow, U".");
+		if (xncol != yncol)
+			Melder_throw (U"When subtracting matrices, their numbers of columns should be equal, instead of ", xncol, U" and ", yncol, U".");
+		double **result = NUMmatrix<double> (1, xnrow, 1, xncol);
+		for (long irow = 1; irow <= xnrow; irow ++) {
+			for (long icol = 1; icol <= xncol; icol ++) {
+				double xvalue = x->numericMatrix.data [irow] [icol];
+				double yvalue = y->numericMatrix.data [irow] [icol];
+				result [irow] [icol] = xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue - yvalue;
+			}
+		}
+		pushNumericMatrix (xnrow, xncol, result);
 	} else if (x->which == Stackel_STRING && y->which == Stackel_STRING) {
 		int64 length1 = str32len (x->string), length2 = str32len (y->string), newlength = length1 - length2;
 		char32 *result;
@@ -2127,10 +2299,71 @@ static void do_sub () {
 	}
 }
 static void do_mul () {
+	/*
+		result.. = x.. * y..
+	*/
 	Stackel y = pop, x = pop;
-	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		pushNumber (x->number == NUMundefined || y->number == NUMundefined ? NUMundefined :
-			x->number * y->number);
+	if (x->which == Stackel_NUMBER) {
+		double xvalue = x->number;
+		if (y->which == Stackel_NUMBER) {
+			/*
+				result = x * y
+			*/
+			double yvalue = y->number;
+			pushNumber (xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue * yvalue);
+		} else if (y->which == Stackel_NUMERIC_VECTOR) {
+			/*
+				result# = x * y#
+			*/
+			long ny = y->numericVector.numberOfElements;
+			double *result = NUMvector<double> (1, ny);
+			if (xvalue == NUMundefined) {
+				for (long i = 1; i <= ny; i ++) {
+					result [i] = NUMundefined;
+				}
+			} else {
+				for (long i = 1; i <= ny; i ++) {
+					double yvalue = y->numericVector.data [i];
+					result [i] = yvalue == NUMundefined ? NUMundefined : xvalue * yvalue;
+				}
+			}
+			pushNumericVector (ny, result);
+		} else if (y->which == Stackel_NUMERIC_MATRIX) {
+			/*
+				result## = x * y##
+			*/
+			long nrow = y->numericMatrix.numberOfRows, ncol = y->numericMatrix.numberOfColumns;
+			double **result = NUMmatrix<double> (1, nrow, 1, ncol);
+			if (xvalue == NUMundefined) {
+				for (long irow = 1; irow <= nrow; irow ++) {
+					for (long icol = 1; icol <= ncol; icol ++) {
+						result [irow] [icol] = NUMundefined;
+					}
+				}
+			} else {
+				for (long irow = 1; irow <= nrow; irow ++) {
+					for (long icol = 1; icol <= ncol; icol ++) {
+						double yvalue = y->numericMatrix.data [irow] [icol];
+						result [irow] [icol] = yvalue == NUMundefined ? NUMundefined : xvalue * yvalue;
+					}
+				}
+			}
+			pushNumericMatrix (nrow, ncol, result);
+		}
+	} else if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_VECTOR) {
+		/*
+			result# = x# * y#
+		*/
+		long nx = x->numericVector.numberOfElements, ny = y->numericVector.numberOfElements;
+		if (nx != ny)
+			Melder_throw (U"When multiplying vectors, their numbers of elements should be equal, instead of ", nx, U" and ", ny, U".");
+		double *result = NUMvector<double> (1, nx);
+		for (long i = 1; i <= nx; i ++) {
+			double xvalue = x->numericVector.data [i];
+			double yvalue = y->numericVector.data [i];
+			result [i] = xvalue == NUMundefined || yvalue == NUMundefined ? NUMundefined : xvalue * yvalue;
+		}
+		pushNumericVector (nx, result);
 	} else {
 		Melder_throw (U"Cannot multiply (*) ", Stackel_whichText (x), U" by ", Stackel_whichText (y), U".");
 	}
@@ -2141,6 +2374,34 @@ static void do_rdiv () {
 		pushNumber (x->number == NUMundefined || y->number == NUMundefined ? NUMundefined :
 			y->number == 0.0 ? NUMundefined :
 			x->number / y->number);
+	} else if (x->which == Stackel_NUMERIC_VECTOR) {
+		if (y->which == Stackel_NUMERIC_VECTOR) {
+			long nelem1 = x->numericVector.numberOfElements, nelem2 = y->numericVector.numberOfElements;
+			if (nelem1 != nelem2)
+				Melder_throw (U"When dividing vectors, their numbers of elements should be equal, instead of ", nelem1, U" and ", nelem2, U".");
+			double *result = NUMvector<double> (1, nelem1);
+			for (long ielem = 1; ielem <= nelem1; ielem ++)
+				result [ielem] = y->numericVector.data [ielem] == 0.0 ? NUMundefined : x->numericVector.data [ielem] / y->numericVector.data [ielem];
+			pushNumericVector (nelem1, result);
+		} else if (y->which == Stackel_NUMBER) {
+			/*
+				result# = x# / y
+			*/
+			long xn = x->numericVector.numberOfElements;
+			double *result = NUMvector<double> (1, xn);
+			double yvalue = y->number;
+			if (yvalue == 0.0) {
+				Melder_throw (U"Cannot divide (/) ", Stackel_whichText (x), U" by zero.");
+			} else {
+				for (long i = 1; i <= xn; i ++) {
+					double xvalue = x->numericVector.data [i];
+					result [i] = xvalue / yvalue;
+				}
+			}
+			pushNumericVector (xn, result);
+		} else {
+			Melder_throw (U"Cannot divide (/) ", Stackel_whichText (x), U" by ", Stackel_whichText (y), U".");
+		}
 	} else {
 		Melder_throw (U"Cannot divide (/) ", Stackel_whichText (x), U" by ", Stackel_whichText (y), U".");
 	}
@@ -2199,6 +2460,59 @@ static void do_function_n_n (double (*f) (double)) {
 			U" requires a numeric argument, not ", Stackel_whichText (x), U".");
 	}
 }
+static void do_functionvec_n_n (double (*f) (double)) {
+	#if 0
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long nelm = x->numericVector.numberOfElements;
+		double *result = NUMvector<double> (1, nelm);
+		for (long i = 1; i <= nelm; i ++) {
+			result [i] = f (x->numericVector.data [i]);
+		}
+		pushNumericVector (nelm, result);
+	} else {
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
+			U" requires a numeric vector argument, not ", Stackel_whichText (x), U".");
+	}
+	#else
+	Stackel x = & theStack [w];
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long nelm = x->numericVector.numberOfElements;
+		for (long i = 1; i <= nelm; i ++) {
+			x->numericVector.data [i] = f (x->numericVector.data [i]);
+		}
+	} else {
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
+			U" requires a numeric vector argument, not ", Stackel_whichText (x), U".");
+	}
+	#endif
+}
+static void do_softmax () {
+	Stackel x = & theStack [w];
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long nelm = x->numericVector.numberOfElements;
+		double maximum = -1e308;
+		for (long i = 1; i <= nelm; i ++) {
+			if (x->numericVector.data [i] > maximum) {
+				maximum = x->numericVector.data [i];
+			}
+		}
+		for (long i = 1; i <= nelm; i ++) {
+			x->numericVector.data [i] -= maximum;
+		}
+		double sum = 0.0;
+		for (long i = 1; i <= nelm; i ++) {
+			x->numericVector.data [i] = exp (x->numericVector.data [i]);
+			sum += x->numericVector.data [i];
+		}
+		for (long i = 1; i <= nelm; i ++) {
+			x->numericVector.data [i] /= sum;
+		}
+	} else {
+		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
+			U" requires a numeric vector argument, not ", Stackel_whichText (x), U".");
+	}
+}
 static void do_abs () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMBER) {
@@ -2231,6 +2545,28 @@ static void do_ceiling () {
 		Melder_throw (U"Cannot round up (ceiling) ", Stackel_whichText (x), U".");
 	}
 }
+static void do_rectify () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMBER) {
+		pushNumber (x->number == NUMundefined ? NUMundefined : x->number > 0.0 ? x->number : 0.0);
+	} else {
+		Melder_throw (U"Cannot rectify ", Stackel_whichText (x), U".");
+	}
+}
+static void do_rectify_numvec () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long nelm = x->numericVector.numberOfElements;
+		double *result = NUMvector<double> (1, nelm);
+		for (long i = 1; i <= nelm; i ++) {
+			double xvalue = x->numericVector.data [i];
+			result [i] = xvalue == NUMundefined ? NUMundefined : xvalue > 0.0 ? xvalue : 0.0;
+		}
+		pushNumericVector (nelm, result);
+	} else {
+		Melder_throw (U"Cannot rectify ", Stackel_whichText (x), U".");
+	}
+}
 static void do_sqrt () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMBER) {
@@ -2298,6 +2634,34 @@ static void do_exp () {
 		Melder_throw (U"Cannot exponentiate (exp) ", Stackel_whichText (x), U".");
 	}
 }
+static void do_exp_numvec () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long nelm = x->numericVector.numberOfElements;
+		double *result = NUMvector<double> (1, nelm);
+		for (long i = 1; i <= nelm; i ++) {
+			result [i] = exp (x->numericVector.data [i]);
+		}
+		pushNumericVector (nelm, result);
+	} else {
+		Melder_throw (U"Cannot exponentiate (exp) ", Stackel_whichText (x), U".");
+	}
+}
+static void do_exp_nummat () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_MATRIX) {
+		long nrow = x->numericMatrix.numberOfRows, ncol = x->numericMatrix.numberOfColumns;
+		double **result = NUMmatrix<double> (1, nrow, 1, ncol);
+		for (long irow = 1; irow <= nrow; irow ++) {
+			for (long icol = 1; icol <= ncol; icol ++) {
+				result [irow] [icol] = exp (x->numericMatrix.data [irow] [icol]);
+			}
+		}
+		pushNumericMatrix (nrow, ncol, result);
+	} else {
+		Melder_throw (U"Cannot exponentiate (exp) ", Stackel_whichText (x), U".");
+	}
+}
 static void do_sinh () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMBER) {
@@ -2349,6 +2713,69 @@ static void do_log10 () {
 		Melder_throw (U"Cannot take the base-10 logarithm (log10) of ", Stackel_whichText (x), U".");
 	}
 }
+static void do_sum () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long numberOfElements = x->numericVector.numberOfElements;
+		double result = 0.0;
+		for (long i = 1; i <= numberOfElements; i ++) {
+			result += x->numericVector.data [i];
+		}
+		pushNumber (result);
+	} else {
+		Melder_throw (U"Cannot compute the sum of ", Stackel_whichText (x), U".");
+	}
+}
+static void do_mean () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long numberOfElements = x->numericVector.numberOfElements;
+		double result = 0.0;
+		for (long i = 1; i <= numberOfElements; i ++) {
+			result += x->numericVector.data [i];
+		}
+		result /= numberOfElements;
+		pushNumber (result);
+	} else {
+		Melder_throw (U"Cannot compute the mean of ", Stackel_whichText (x), U".");
+	}
+}
+static void do_stdev () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long numberOfElements = x->numericVector.numberOfElements;
+		double mean = 0.0;
+		for (long i = 1; i <= numberOfElements; i ++) {
+			mean += x->numericVector.data [i];
+		}
+		mean /= numberOfElements;
+		double result = 0.0;
+		for (long i = 1; i <= numberOfElements; i ++) {
+			double diff = x->numericVector.data [i] - mean;
+			result += diff * diff;
+		}
+		result /= numberOfElements - 1;
+		result = sqrt (result);
+		pushNumber (result);
+	} else {
+		Melder_throw (U"Cannot compute the mean of ", Stackel_whichText (x), U".");
+	}
+}
+static void do_center () {
+	Stackel x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR) {
+		long numberOfElements = x->numericVector.numberOfElements;
+		double result = 0.0, sumOfWeights = 0.0;
+		for (long i = 1; i <= numberOfElements; i ++) {
+			result += i * x->numericVector.data [i];
+			sumOfWeights += x->numericVector.data [i];
+		}
+		result /= sumOfWeights;
+		pushNumber (result);
+	} else {
+		Melder_throw (U"Cannot compute the center of ", Stackel_whichText (x), U".");
+	}
+}
 static void do_function_dd_d (double (*f) (double, double)) {
 	Stackel y = pop, x = pop;
 	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
@@ -2624,19 +3051,39 @@ static void do_doStr () {
 	praat_updateSelection ();   // BUG: superfluous? flickering?
 	pushString (Melder_dup (U""));
 }
+static void shared_do_writeInfo (int numberOfArguments) {
+	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
+		Stackel arg = & theStack [w + iarg];
+		if (arg->which == Stackel_NUMBER) {
+			MelderInfo_write (arg->number);
+		} else if (arg->which == Stackel_STRING) {
+			MelderInfo_write (arg->string);
+		} else if (arg->which == Stackel_NUMERIC_VECTOR) {
+			long numberOfElements = arg->numericVector.numberOfElements;
+			double *data = arg->numericVector.data;
+			for (long i = 1; i <= numberOfElements; i ++) {
+				MelderInfo_write (data [i], i == numberOfElements ? U"" : U" ");
+			}
+		} else if (arg->which == Stackel_NUMERIC_MATRIX) {
+			long numberOfRows = arg->numericMatrix.numberOfRows;
+			long numberOfColumns = arg->numericMatrix.numberOfColumns;
+			double **data = arg->numericMatrix.data;
+			for (long irow = 1; irow <= numberOfRows; irow ++) {
+				for (long icol = 1; icol <= numberOfRows; icol ++) {
+					MelderInfo_write (data [irow] [icol], icol == numberOfColumns ? U"" : U" ");
+				}
+				MelderInfo_write (irow == numberOfRows ? U"" : U"\n");
+			}
+		}
+	}
+}
 static void do_writeInfo () {
 	Stackel narg = pop;
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = lround (narg->number);
 	w -= numberOfArguments;
 	MelderInfo_open ();
-	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
-		Stackel arg = & theStack [w + iarg];
-		if (arg->which == Stackel_NUMBER)
-			MelderInfo_write (arg->number);
-		else if (arg->which == Stackel_STRING)
-			MelderInfo_write (arg->string);
-	}
+	shared_do_writeInfo (numberOfArguments);
 	MelderInfo_drain ();
 	pushNumber (1);
 }
@@ -2646,13 +3093,7 @@ static void do_writeInfoLine () {
 	int numberOfArguments = lround (narg->number);
 	w -= numberOfArguments;
 	MelderInfo_open ();
-	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
-		Stackel arg = & theStack [w + iarg];
-		if (arg->which == Stackel_NUMBER)
-			MelderInfo_write (arg->number);
-		else if (arg->which == Stackel_STRING)
-			MelderInfo_write (arg->string);
-	}
+	shared_do_writeInfo (numberOfArguments);
 	MelderInfo_write (U"\n");
 	MelderInfo_drain ();
 	pushNumber (1);
@@ -2662,13 +3103,7 @@ static void do_appendInfo () {
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = lround (narg->number);
 	w -= numberOfArguments;
-	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
-		Stackel arg = & theStack [w + iarg];
-		if (arg->which == Stackel_NUMBER)
-			MelderInfo_write (arg->number);
-		else if (arg->which == Stackel_STRING)
-			MelderInfo_write (arg->string);
-	}
+	shared_do_writeInfo (numberOfArguments);
 	MelderInfo_drain ();
 	pushNumber (1);
 }
@@ -2677,13 +3112,7 @@ static void do_appendInfoLine () {
 	Melder_assert (narg->which == Stackel_NUMBER);
 	int numberOfArguments = lround (narg->number);
 	w -= numberOfArguments;
-	for (int iarg = 1; iarg <= numberOfArguments; iarg ++) {
-		Stackel arg = & theStack [w + iarg];
-		if (arg->which == Stackel_NUMBER)
-			MelderInfo_write (arg->number);
-		else if (arg->which == Stackel_STRING)
-			MelderInfo_write (arg->string);
-	}
+	shared_do_writeInfo (numberOfArguments);
 	MelderInfo_write (U"\n");
 	MelderInfo_drain ();
 	pushNumber (1);
@@ -2951,29 +3380,44 @@ static void do_imin () {
 	pushNumber (result);
 }
 static void do_imax () {
-	Stackel n = pop, last;
-	double maximum, result;
+	Stackel n = pop;
 	Melder_assert (n->which == Stackel_NUMBER);
 	if (n->number < 1)
 		Melder_throw (U"The function \"imax\" requires at least one argument.");
-	last = pop;
-	if (last->which != Stackel_NUMBER)
-		Melder_throw (U"The function \"imax\" can only have numeric arguments, not ", Stackel_whichText (last), U".");
-	maximum = last->number;
-	result = n->number;
-	for (int j = lround (n->number) - 1; j > 0; j --) {
-		Stackel previous = pop;
-		if (previous->which != Stackel_NUMBER)
-			Melder_throw (U"The function \"imax\" can only have numeric arguments, not ", Stackel_whichText (previous), U".");
-		if (maximum == NUMundefined || previous->number == NUMundefined) {
-			maximum = NUMundefined;
-			result = NUMundefined;
-		} else if (previous->number > maximum) {
-			maximum = previous->number;
-			result = j;
+	Stackel last = pop;
+	if (last->which == Stackel_NUMBER) {
+		double maximum = last->number;
+		double result = n->number;
+		for (int j = lround (n->number) - 1; j > 0; j --) {
+			Stackel previous = pop;
+			if (previous->which != Stackel_NUMBER)
+				Melder_throw (U"The function \"imax\" can only have numeric arguments, not ", Stackel_whichText (previous), U".");
+			if (maximum == NUMundefined || previous->number == NUMundefined) {
+				maximum = NUMundefined;
+				result = NUMundefined;
+			} else if (previous->number > maximum) {
+				maximum = previous->number;
+				result = j;
+			}
+		}
+		pushNumber (result);
+	} else if (last->which == Stackel_NUMERIC_VECTOR) {
+		if (n->number != 1)
+			Melder_throw (U"The function \"imax\" requires exactly one vector argument.");
+		long numberOfElements = last->numericVector.numberOfElements;
+		long result = 1;
+		double maximum = last->numericVector.data [1];
+		for (long i = 2; i <= numberOfElements; i ++) {
+			if (last->numericVector.data [i] > maximum) {
+				result = i;
+				maximum = last->numericVector.data [i];
+			}
 		}
+		pushNumber (result);
+	} else {
+		Stackel nn = pop;
+		Melder_throw (U"Cannot compute the imax of ", Stackel_whichText (nn), U".");
 	}
-	pushNumber (result);
 }
 static void do_zeroNumvec () {
 	Stackel n = pop;
@@ -3737,7 +4181,7 @@ static void do_deleteFile () {
 		Melder_throw (U"The function \"deleteFile\" is not available inside manuals.");
 	Stackel f = pop;
 	if (f->which == Stackel_STRING) {
-		structMelderFile file = { 0 };
+		structMelderFile file { 0 };
 		Melder_relativePathToFile (f->string, & file);
 		MelderFile_delete (& file);
 		pushNumber (1);
@@ -3750,7 +4194,7 @@ static void do_createDirectory () {
 		Melder_throw (U"The function \"createDirectory\" is not available inside manuals.");
 	Stackel f = pop;
 	if (f->which == Stackel_STRING) {
-		structMelderDir currentDirectory = { { 0 } };
+		structMelderDir currentDirectory { { 0 } };
 		Melder_getDefaultDir (& currentDirectory);
 		#if defined (UNIX) || defined (macintosh)
 			Melder_createDirectory (& currentDirectory, f->string, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
@@ -3774,7 +4218,7 @@ static void do_variableExists () {
 static void do_readFile () {
 	Stackel f = pop;
 	if (f->which == Stackel_STRING) {
-		structMelderFile file = { 0 };
+		structMelderFile file { 0 };
 		Melder_relativePathToFile (f->string, & file);
 		autostring32 text = MelderFile_readText (& file);
 		pushNumber (Melder_atof (text.peek()));
@@ -3785,7 +4229,7 @@ static void do_readFile () {
 static void do_readFileStr () {
 	Stackel f = pop;
 	if (f->which == Stackel_STRING) {
-		structMelderFile file = { 0 };
+		structMelderFile file { 0 };
 		Melder_relativePathToFile (f->string, & file);
 		autostring32 text = MelderFile_readText (& file);
 		pushString (text.transfer());
@@ -3793,6 +4237,117 @@ static void do_readFileStr () {
 		Melder_throw (U"The function \"readFile$\" requires a string (a file name), not ", Stackel_whichText (f), U".");
 	}
 }
+static void do_outerNummat () {
+	/*
+		result## = outer## (x#, y#)
+	*/
+	Stackel y = pop, x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_VECTOR) {
+		long xn = x->numericVector.numberOfElements, yn = y->numericVector.numberOfElements;
+		double **result = NUMmatrix<double> (1, xn, 1, yn);
+		for (long irow = 1; irow <= xn; irow ++) {
+			double xvalue = x->numericVector.data [irow];
+			if (xvalue == NUMundefined) {
+				for (long icol = 1; icol <= yn; icol ++) {
+					result [irow] [icol] = NUMundefined;
+				}
+			} else {
+				for (long icol = 1; icol <= yn; icol ++) {
+					double yvalue = y->numericVector.data [icol];
+					result [irow] [icol] = /*yvalue == NUMundefined ? NUMundefined :*/ xvalue * yvalue;
+				}
+			}
+		}
+		pushNumericMatrix (xn, yn, result);
+	} else {
+		Melder_throw (U"The function \"outer##\" requires two vectors, not ", Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
+	}
+}
+static void do_mulNumvec () {
+	/*
+		result# = mul# (x.., y..)
+	*/
+	Stackel y = pop, x = pop;
+	if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_MATRIX) {
+		/*
+			result# = mul# (x#, y##)
+		*/
+		long xn = x->numericVector.numberOfElements, ynrow = y->numericMatrix.numberOfRows, yncol = y->numericMatrix.numberOfColumns;
+		if (ynrow != xn)
+			Melder_throw (U"In the function \"mul#\", the dimension of the vector and the number of rows of the matrix should be equal, "
+				"not ", xn, U" and ", ynrow);
+		bool xundefined = false;
+		for (long i = 1; i <= xn; i ++) {
+			double xvalue = x->numericVector.data [i];
+			if (xvalue == NUMundefined) {
+				xundefined = true;
+				break;
+			}
+		}
+		double *result = NUMvector<double> (1, yncol);
+		if (xundefined) {
+			for (long j = 1; j <= yncol; j ++) {
+				result [j] = NUMundefined;
+			}
+		} else {
+			for (long j = 1; j <= yncol; j ++) {
+				result [j] = 0.0;
+				for (long i = 1; i <= ynrow; i ++) {
+					double xvalue = x->numericVector.data [i];
+					double yvalue = y->numericMatrix.data [i] [j];
+					#if 0
+					if (yvalue == NUMundefined) {
+						result [j] = NUMundefined;
+						break;
+					}
+					#endif
+					result [j] += xvalue * yvalue;
+				}
+			}
+		}
+		pushNumericVector (yncol, result);
+	} else if (x->which == Stackel_NUMERIC_MATRIX && y->which == Stackel_NUMERIC_VECTOR) {
+		/*
+			result# = mul# (x##, y#)
+		*/
+		long xnrow = x->numericMatrix.numberOfRows, xncol = x->numericMatrix.numberOfColumns, yn = y->numericVector.numberOfElements;
+		if (yn != xncol)
+			Melder_throw (U"In the function \"mul#\", the the number of columns of the matrix and the dimension of the vector should be equal, "
+				"not ", xncol, U" and ", yn);
+		bool yundefined = false;
+		for (long i = 1; i <= yn; i ++) {
+			double yvalue = y->numericVector.data [i];
+			if (yvalue == NUMundefined) {
+				yundefined = true;
+				break;
+			}
+		}
+		double *result = NUMvector<double> (1, xnrow);
+		if (yundefined) {
+			for (long i = 1; i <= xnrow; i ++) {
+				result [i] = NUMundefined;
+			}
+		} else {
+			for (long i = 1; i <= xnrow; i ++) {
+				result [i] = 0.0;
+				for (long j = 1; j <= xncol; j ++) {
+					double xvalue = x->numericMatrix.data [i] [j];
+					double yvalue = y->numericVector.data [j];
+					#if 0
+					if (xvalue == NUMundefined) {
+						result [i] = NUMundefined;
+						break;
+					}
+					#endif
+					result [i] += xvalue * yvalue;
+				}
+			}
+		}
+		pushNumericVector (xnrow, result);
+	} else {
+		Melder_throw (U"The function \"mul#\" requires a vector and a matrix, not ", Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
+	}
+}
 static void do_beginPauseForm () {
 	if (theCurrentPraatObjects != & theForegroundPraatObjects)
 		Melder_throw (U"The function \"beginPauseForm\" is not available inside manuals.");
@@ -4880,11 +5435,13 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case MOD_: { do_mod ();
 } break; case MINUS_: { do_minus ();
 } break; case POWER_: { do_power ();
-/********** Functions of 1 numerical variable: **********/
+/********** Functions of 1 variable: **********/
 } break; case ABS_: { do_abs ();
 } break; case ROUND_: { do_round ();
 } break; case FLOOR_: { do_floor ();
 } break; case CEILING_: { do_ceiling ();
+} break; case RECTIFY_: { do_rectify ();
+} break; case RECTIFY_NUMVEC_: { do_rectify_numvec ();
 } break; case SQRT_: { do_sqrt ();
 } break; case SIN_: { do_sin ();
 } break; case COS_: { do_cos ();
@@ -4895,6 +5452,8 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case SINC_: { do_function_n_n (NUMsinc);
 } break; case SINCPI_: { do_function_n_n (NUMsincpi);
 } break; case EXP_: { do_exp ();
+} break; case EXP_NUMVEC_: { do_exp_numvec ();
+} break; case EXP_NUMMAT_: { do_exp_nummat ();
 } break; case SINH_: { do_sinh ();
 } break; case COSH_: { do_cosh ();
 } break; case TANH_: { do_tanh ();
@@ -4902,12 +5461,16 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case ARCCOSH_: { do_function_n_n (NUMarccosh);
 } break; case ARCTANH_: { do_function_n_n (NUMarctanh);
 } break; case SIGMOID_: { do_function_n_n (NUMsigmoid);
+} break; case SIGMOID_NUMVEC_: { do_functionvec_n_n (NUMsigmoid);
+} break; case SOFTMAX_NUMVEC_: { do_softmax ();
 } break; case INV_SIGMOID_: { do_function_n_n (NUMinvSigmoid);
 } break; case ERF_: { do_function_n_n (NUMerf);
 } break; case ERFC_: { do_function_n_n (NUMerfcc);
 } break; case GAUSS_P_: { do_function_n_n (NUMgaussP);
 } break; case GAUSS_Q_: { do_function_n_n (NUMgaussQ);
 } break; case INV_GAUSS_Q_: { do_function_n_n (NUMinvGaussQ);
+} break; case RANDOM_BERNOULLI_: { do_function_n_n (NUMrandomBernoulli_real);
+} break; case RANDOM_BERNOULLI_NUMVEC_: { do_functionvec_n_n (NUMrandomBernoulli_real);
 } break; case RANDOM_POISSON_: { do_function_n_n (NUMrandomPoisson);
 } break; case LOG2_: { do_log2 ();
 } break; case LN_: { do_ln ();
@@ -4924,6 +5487,10 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case ERB_: { do_function_n_n (NUMerb);
 } break; case HERTZ_TO_ERB_: { do_function_n_n (NUMhertzToErb);
 } break; case ERB_TO_HERTZ_: { do_function_n_n (NUMerbToHertz);
+} break; case SUM_: { do_sum ();
+} break; case MEAN_: { do_mean ();
+} break; case STDEV_: { do_stdev ();
+} break; case CENTER_: { do_center ();
 /********** Functions of 2 numerical variables: **********/
 } break; case ARCTAN2_: { do_function_dd_d (atan2);
 } break; case RANDOM_UNIFORM_: { do_function_dd_d (NUMrandomUniform);
@@ -5024,6 +5591,9 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case VARIABLE_EXISTS_: { do_variableExists ();
 } break; case READ_FILE_: { do_readFile ();
 } break; case READ_FILESTR_: { do_readFileStr ();
+/********** Matrix functions: **********/
+} break; case OUTER_NUMMAT_: { do_outerNummat ();
+} break; case MUL_NUMVEC_: { do_mulNumvec ();
 /********** Pause window functions: **********/
 } break; case BEGIN_PAUSE_FORM_: { do_beginPauseForm ();
 } break; case PAUSE_FORM_ADD_REAL_: { do_pauseFormAddReal ();
diff --git a/sys/Graphics_colour.cpp b/sys/Graphics_colour.cpp
index 5a0ad57..c1e3de3 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 > 101105 );
+				bool cacheImageInRectWillWork = ( Melder_systemVersion < 101100 || Melder_systemVersion > 101106 );
 				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 > 101105 );
+				bool cacheImageInRectWillWork = ( Melder_systemVersion < 101100 || Melder_systemVersion > 101106 );
 				if (cacheImageInRectWillWork) {
 					NSView *nsView = my d_macView;
 					if (direction == 1) {
diff --git a/sys/GuiShell.cpp b/sys/GuiShell.cpp
index 5fe6164..5d683ee 100644
--- a/sys/GuiShell.cpp
+++ b/sys/GuiShell.cpp
@@ -1,6 +1,6 @@
 /* GuiShell.cpp
  *
- * Copyright (C) 1993-2012,2015 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1993-2012,2015,2016 Paul Boersma, 2013 Tom Naughton
  *
  * 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
@@ -41,6 +41,7 @@ Thing_implement (GuiShell, GuiForm, 0);
 	}
 	- (void) keyDown: (NSEvent *) theEvent {
 		trace (U"key down");
+		[super keyDown: theEvent];   // for automatic behaviour in dialog boxes; do GuiWindows have to override this to do nothing?
 	}
 	- (BOOL) windowShouldClose: (id) sender {
 		GuiCocoaShell *widget = (GuiCocoaShell *) sender;
diff --git a/sys/ScriptEditor.cpp b/sys/ScriptEditor.cpp
index 9eacc96..844ecec 100644
--- a/sys/ScriptEditor.cpp
+++ b/sys/ScriptEditor.cpp
@@ -353,4 +353,11 @@ autoScriptEditor ScriptEditor_createFromScript_canBeNull (Editor environment, Sc
 	}
 }
 
+void ScriptEditor_debug_printAllOpenScriptEditors () {
+	for (long ieditor = 1; ieditor <= theReferencesToAllOpenScriptEditors.size; ieditor ++) {
+		ScriptEditor editor = theReferencesToAllOpenScriptEditors.at [ieditor];
+		Melder_casual (U"Open script editor #", ieditor, U": <<", & editor -> file, U">>");
+	}
+}
+
 /* End of file ScriptEditor.cpp */
diff --git a/sys/ScriptEditor.h b/sys/ScriptEditor.h
index 80a2263..1838860 100644
--- a/sys/ScriptEditor.h
+++ b/sys/ScriptEditor.h
@@ -2,7 +2,7 @@
 #define _ScriptEditor_h_
 /* ScriptEditor.h
  *
- * Copyright (C) 1997-2011,2012,2015 Paul Boersma
+ * Copyright (C) 1997-2011,2012,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
@@ -58,5 +58,7 @@ autoScriptEditor ScriptEditor_createFromScript_canBeNull (
 
 bool ScriptEditors_dirty ();   // are there any modified and unsaved scripts? Ask before quitting the program.
 
+void ScriptEditor_debug_printAllOpenScriptEditors ();
+
 /* End of file ScriptEditor.h */
 #endif
diff --git a/sys/melder_audio.cpp b/sys/melder_audio.cpp
index 12a5139..1f799b1 100644
--- a/sys/melder_audio.cpp
+++ b/sys/melder_audio.cpp
@@ -1202,7 +1202,9 @@ void MelderAudio_play16 (int16_t *buffer, long sampleRate, long numberOfSamples,
 			// without a timer, on my computer the workproc would be called almost once in every sampling period.
 			// Such frequent updates are not necessary, some 50 updates a second is fast enough for displayong a runnning cursor
 			// the timeout will be automatically stopped if workProc_gtk returns false.
-			my workProcId_gtk = g_timeout_add (20, workProc_gtk, nullptr);
+			#ifndef NO_GRAPHICS
+				my workProcId_gtk = g_timeout_add (20, workProc_gtk, nullptr);
+			#endif
 		}
 	#endif
 	} else {
diff --git a/sys/melder_debug.cpp b/sys/melder_debug.cpp
index 6f9f14b..f0daf6b 100644
--- a/sys/melder_debug.cpp
+++ b/sys/melder_debug.cpp
@@ -1,6 +1,6 @@
 /* melder_debug.cpp
  *
- * Copyright (C) 2000-2012,2014,2015 Paul Boersma
+ * Copyright (C) 2000-2012,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
@@ -605,7 +605,7 @@ void Melder_trace (const char *fileName, int lineNumber, const char *functionNam
 	Melder_trace_close (f);
 }
 
-#ifdef linux
+#if defined (linux) && ! defined (NO_GRAPHICS)
 static void theGtkLogHandler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer unused_data) {
 	FILE *f = Melder_trace_open (nullptr, 0, "GTK");
 	fprintf (f, "%s", message);
@@ -633,7 +633,7 @@ void Melder_setTracing (bool tracing) {
 			U" at ", Melder_peek8to32 (ctime (& today))
 		);
 	Melder_isTracing = tracing;
-	#ifdef linux
+	#if defined (linux) && ! defined (NO_GRAPHICS)
 		static guint handler_id1, handler_id2, handler_id3;
 		if (tracing) {
 			handler_id1 = g_log_set_handler ("Gtk",          (GLogLevelFlags) (G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), theGtkLogHandler,         nullptr);
diff --git a/sys/melder_readtext.cpp b/sys/melder_readtext.cpp
index 34d0af8..de89840 100644
--- a/sys/melder_readtext.cpp
+++ b/sys/melder_readtext.cpp
@@ -169,16 +169,26 @@ static char32 * _MelderFile_readText (MelderFile file, char **string8) {
 				type = 1;   // big-endian 16-bit
 			} else if (firstByte == 0xFF && secondByte == 0xFE) {
 				type = 2;   // little-endian 16-bit
+			} else if (firstByte == 0xEF && secondByte == 0xBB && length >= 3) {
+				int thirdByte = fgetc (f);
+				if (thirdByte == 0xBF) {
+					type = -1;   // UTF-8 with BOM
+				}
 			}
 		}
-		if (type == 0) {
-			rewind (f);   // length and type already set correctly.
+		if (type <= 0) {
+			if (type == -1) {
+				length -= 3;
+				fseeko (f, 3, SEEK_SET);
+			} else {
+				rewind (f);   // length and type already set correctly.
+			}
 			autostring8 text8bit = Melder_malloc (char, length + 1);
 			Melder_assert (text8bit.peek());
 			size_t numberOfBytesRead = fread_multi (text8bit.peek(), (size_t) length, f);
 			if ((int64) numberOfBytesRead < length)
-				Melder_throw (U"The file contains ", length, U" bytes, but we could read only ",
-					numberOfBytesRead, U" of them.");
+				Melder_throw (U"The file contains ", length, U" bytes", type == -1 ? U" after the byte-order mark" : U"",
+					U", but we could read only ", numberOfBytesRead, U" of them.");
 			text8bit [length] = '\0';
 			/*
 			 * Count and repair null bytes.
diff --git a/sys/praat_version.h b/sys/praat_version.h
index 83b4738..3886ba7 100644
--- a/sys/praat_version.h
+++ b/sys/praat_version.h
@@ -1,5 +1,5 @@
-#define PRAAT_VERSION_STR 6.0.19
-#define PRAAT_VERSION_NUM 6019
+#define PRAAT_VERSION_STR 6.0.21
+#define PRAAT_VERSION_NUM 6021
 #define PRAAT_YEAR 2016
-#define PRAAT_MONTH June
-#define PRAAT_DAY 13
+#define PRAAT_MONTH September
+#define PRAAT_DAY 25
diff --git a/sys/sendsocket.c b/sys/sendsocket.c
index 6138c8f..0a7f8df 100644
--- a/sys/sendsocket.c
+++ b/sys/sendsocket.c
@@ -1,6 +1,6 @@
 /* sendsocket.c
  *
- * Copyright (C) 1999-2006 Paul Boersma
+ * Copyright (C) 1999-2006,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
@@ -16,38 +16,35 @@
  * along with this work. If not, see <http://www.gnu.org/licenses/>.
  */
 
-/*
- * pb 1999/12/21
- * pb 2002/03/07 GPL
- * pb 2003/12/19 return NULL if no error
- * pb 2004/10/11 re-included windows.h include file (undoes fix of CW5 include-file bug)
- * pb 2004/10/11 user-readable error messages
- * pb 2006/10/28 erased MacOS 9 stuff
- */
-
-#include <string.h>
-#include <stdio.h>
 #include <stdlib.h>
-#if defined (UNIX) || defined (macintosh)
-	#if defined (macintosh)
-		/* Prevent some warnings with MacOS X 10.4.2 / CodeWarrior 9.6 / Xcode 2.0 */
-		#define GATEWAY 0
-		#define SENDFILE 0
+#ifdef NO_NETWORK
+	/*
+		If Praat is to be linked statically for reasons of having old C and C++ libraries
+		on the target system, then it cannot use networking functions, because these tend
+		to require the library version that was used for linking (on Linux).
+	*/
+#else
+	#include <string.h>
+	#include <stdio.h>
+	#if defined (UNIX) || defined (macintosh)
+		#include <sys/types.h>
+		#include <unistd.h>
+		#include <ctype.h>
+		#include <netdb.h>
+		#include <sys/socket.h>
+		#include <netinet/in.h>
+		#include <arpa/inet.h>
+	#elif defined (_WIN32)
+		#include <windows.h>
+		#include <winsock.h>
 	#endif
-	#include <sys/types.h>
-	#include <unistd.h>
-	#include <ctype.h>
-	#include <netdb.h>
-	#include <sys/socket.h>
-	#include <netinet/in.h>
-	#include <arpa/inet.h>
-#elif defined (_WIN32)
-	#include <windows.h>
-	#include <winsock.h>
 #endif
 #include "sendsocket.h"
 
 char * sendsocket (const char *hostNameAndPort, const char *command) {
+#ifdef NO_NETWORK
+	return NULL;
+#else
 	static char result [200];
 	char hostName [61], *colon;
 	int port;
@@ -130,6 +127,7 @@ end:
 	}
 }
 	return result [0] == '\0' ? NULL: result;
+#endif
 }
 
 /* End of file sendsocket.c */
diff --git a/test/script/RBM.praat b/test/script/RBM.praat
new file mode 100644
index 0000000..b0db6f8
--- /dev/null
+++ b/test/script/RBM.praat
@@ -0,0 +1,165 @@
+writeInfoLine: "Training two RBM's with a three-peaked distribution"
+stopwatch
+
+numberOfInputNodes = 30
+numberOfMiddleNodes = 50
+numberOfOutputNodes = 20
+numberOfVowels = 3
+mean# = zero# (3)
+mean# [1] = 8
+mean# [2] = 16
+mean# [3] = 23
+sigma = 1.8
+numberOfPatterns = 10000
+learningRate = 0.001
+
+#
+# Train first layer.
+#
+input1# = zero# (numberOfInputNodes)
+output1# = zero# (numberOfMiddleNodes)
+inbias1# = zero# (numberOfInputNodes)
+outbias1# = zero# (numberOfMiddleNodes)
+weight1## = zero## (numberOfInputNodes, numberOfMiddleNodes)
+inrec1# = zero# (numberOfInputNodes)
+outrec1# = zero# (numberOfMiddleNodes)
+for idatum to numberOfPatterns
+	vowel = randomInteger (1, numberOfVowels)
+	formant = randomGauss (mean# [vowel], sigma)
+	for i to numberOfInputNodes
+		input1# [i] = 5 * exp (-0.5 * ((i - formant) / sigma) ^ 2) - 0.5
+	endfor
+	#
+	# Spread up, with Bernoulli sampling.
+	#
+	output1# = sigmoid# (outbias1# + mul# (input1#, weight1##))
+	for j to numberOfMiddleNodes
+		output1# [j] = ( randomUniform (0, 1) < output1# [j] )
+		;output1# [j] = randomBernoulli (output1# [j])
+	endfor
+	#
+	# Spread down.
+	#
+	inrec1# = inbias1# + mul# (weight1##, output1#)   ; Gaussian
+	#
+	# Spread up.
+	#
+	outrec1# = sigmoid# (outbias1# + mul# (inrec1#, weight1##))
+	#
+	# Update.
+	#
+	inbias1# = inbias1# + learningRate * (input1# - inrec1#)
+	outbias1# = outbias1# + learningRate * (output1# - outrec1#)
+	weight1## = weight1## + learningRate * (outer## (input1#, output1#) - outer## (inrec1#, outrec1#))
+endfor
+
+#
+# Train second layer.
+#
+input2# = zero# (numberOfMiddleNodes)
+output2# = zero# (numberOfOutputNodes)
+inbias2# = zero# (numberOfMiddleNodes)
+outbias2# = zero# (numberOfOutputNodes)
+weight2## = zero## (numberOfMiddleNodes, numberOfOutputNodes)
+inrec2# = zero# (numberOfMiddleNodes)
+outrec2# = zero# (numberOfOutputNodes)
+for idatum to numberOfPatterns
+	vowel = randomInteger (1, numberOfVowels)
+	formant = randomGauss (mean# [vowel], sigma)
+	for i to numberOfInputNodes
+		input1# [i] = 5 * exp (-0.5 * ((i - formant) / sigma) ^ 2) - 0.5
+	endfor
+	#
+	# Spread up through first layer, with Bernoulli sampling.
+	#
+	output1# = sigmoid# (outbias1# + mul# (input1#, weight1##))
+	for j to numberOfMiddleNodes
+		output1# [j] = ( randomUniform (0, 1) < output1# [j] )
+		;output1# [j] = randomBernoulli (output1# [j])
+	endfor
+	#
+	# Copy output of first layer to input of second layer.
+	#
+	input2# = output1#
+	#
+	# Spread up through second layer, with Bernoulli sampling.
+	#
+	output2# = sigmoid# (outbias2# + mul# (input2#, weight2##))
+	for j to numberOfOutputNodes
+		output2# [j] = ( randomUniform (0, 1) < output2# [j] )
+		;output2# [j] = randomBernoulli (output2# [j])
+	endfor
+	#
+	# Spread down.
+	#
+	inrec2# = sigmoid# (inbias2# + mul# (weight2##, output2#))
+	#
+	# Spread up.
+	#
+	outrec2# = sigmoid# (outbias2# + mul# (inrec2#, weight2##))
+	#
+	# Update.
+	#
+	inbias2# = inbias2# + learningRate * (input2# - inrec2#)
+	outbias2# = outbias2# + learningRate * (output2# - outrec2#)
+	weight2## = weight2## + learningRate * (outer## (input2#, output2#) - outer## (inrec2#, outrec2#))
+endfor
+
+appendInfoLine: "Trained in ", stopwatch, " seconds"
+
+numberOfTestPatterns = 15
+Erase all
+Font size: 10
+for itest to numberOfTestPatterns
+	appendInfoLine: "Test pattern #", itest, ":"
+	vowel = randomInteger (1, numberOfVowels)
+	formant = randomGauss (mean# [vowel], sigma)
+	for i to numberOfInputNodes
+		input1# [i] = 5 * exp (-0.5 * ((i - formant) / sigma) ^ 2) - 0.5
+	endfor
+	#
+	# Draw input.
+	#
+	Select outer viewport: 0, 3, (itest - 1) * 0.6, (itest - 1) * 0.6 + 1.0
+	Create simple Matrix: "input", 1, numberOfInputNodes, "5 * exp (-0.5 * ((col - formant) / sigma) ^ 2) - 0.5"
+	stdev = Get standard deviation: 0, 0, 0, 0
+	appendInfoLine: "   Energy in input layer: ", stdev
+	Draw rows: 0, 0, 0, 0, -5, 5
+	Remove
+	#
+	# Spread up through first layer, without Bernoulli sampling.
+	#
+	output1# = sigmoid# (outbias1# + mul# (input1#, weight1##))
+	mean = sumOver (i to numberOfMiddleNodes, output1# [i]) / numberOfMiddleNodes
+	stdev = sqrt (sumOver (i to numberOfMiddleNodes, (output1# [i] - mean) ^ 2) / (numberOfMiddleNodes - 1))
+	appendInfoLine: "   Energy in middle layer: ", stdev
+	#
+	# Copy output of first layer to input of second layer.
+	#
+	input2# = output1#
+	#
+	# Spread up through second layer, without Bernoulli sampling.
+	#
+	output2# = sigmoid# (outbias2# + mul# (input2#, weight2##))
+	mean = sumOver (i to numberOfOutputNodes, output2# [i]) / numberOfOutputNodes
+	stdev = sqrt (sumOver (i to numberOfOutputNodes, (output2# [i] - mean) ^ 2) / (numberOfOutputNodes - 1))
+	appendInfoLine: "   Energy in output layer: ", stdev
+	#
+	# Spread down through second layer.
+	#
+	inrec2# = sigmoid# (inbias2# + mul# (weight2##, output2#))
+	mean = sumOver (i to numberOfMiddleNodes, inrec2# [i]) / numberOfMiddleNodes
+	stdev = sqrt (sumOver (i to numberOfMiddleNodes, (inrec2# [i] - mean) ^ 2) / (numberOfMiddleNodes - 1))
+	appendInfoLine: "   Energy in middle layer: ", stdev
+	#
+	# Spread down through first layer.
+	#
+	inrec1# = inbias1# + mul# (weight1##, inrec2#)   ; Gaussian
+	#
+	# Draw reflection.
+	#
+	Select outer viewport: 3, 6, (itest - 1) * 0.6, (itest - 1) * 0.6 + 1.0
+	Create simple Matrix: "reflection", 1, numberOfInputNodes, "inrec1# [col]"
+	Draw rows: 0, 0, 0, 0, -5, 5
+	Remove
+endfor
diff --git a/test/script/arrays.praat b/test/script/arrays.praat
index 4e866c8..35c4fd1 100644
--- a/test/script/arrays.praat
+++ b/test/script/arrays.praat
@@ -47,6 +47,12 @@ b = d# [99]
 c = d# [100]
 printline 'a' 'b' 'c'
 
+e# = a# + a#
+assert e# [3] = 8
+
+asserterror numbers of elements should be equal
+e# = a# + d#
+
 ; q### =
 ; data####
 ;e# = d# + c#
diff --git a/test/script/softmax.praat b/test/script/softmax.praat
new file mode 100644
index 0000000..b8cfea0
--- /dev/null
+++ b/test/script/softmax.praat
@@ -0,0 +1,6 @@
+a# = zero# (3)
+a#[1]=3
+a#[2]=2
+a#[3]=4
+output#=softmax#(a#)
+writeInfoLine: output#[1], " ", output#[2], " ", output#[3]
\ No newline at end of file

-- 
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