[med-svn] [praat] 01/09: New upstream version 6.0.30

Rafael Laboissiere rafael at debian.org
Fri Aug 11 20:49:31 UTC 2017


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

rafael pushed a commit to branch master
in repository praat.

commit 6f7509931577a60d35205570a427793a6b2d9601
Author: Rafael Laboissiere <rafael at debian.org>
Date:   Wed Aug 9 07:46:43 2017 -0300

    New upstream version 6.0.30
---
 EEG/praat_EEG.cpp                           |  36 +-
 FFNet/Makefile                              |   4 +-
 LPC/LPC_to_Spectrum.cpp                     |   2 +-
 LPC/Makefile                                |   4 +-
 README.md                                   |  12 +
 artsynth/Makefile                           |   4 +-
 artsynth/praat_Artsynth.cpp                 |   9 +-
 contrib/ola/Makefile                        |   4 +-
 dwsys/DoublyLinkedList.cpp                  |   2 +-
 dwsys/Makefile                              |   4 +-
 dwsys/NUM2.cpp                              |  37 +-
 dwsys/NUM2.h                                |  25 +-
 dwsys/NUMcomplex.cpp                        | 206 +++++++
 dwsys/NUMcomplex.h                          |  31 ++
 dwsys/NUMlapack.cpp                         |  10 +-
 dwsys/NUMstring.cpp                         |   2 +-
 dwtest/test_DTW.praat                       |   2 +-
 dwtest/test_Sound_draw_where.praat          |  51 +-
 dwtest/test_gammatonefilter.praat           |  52 ++
 dwtest/test_textGrid_and_DurationTier.praat |  61 ++
 dwtools/DTW.cpp                             | 635 +++++++++++----------
 dwtools/DTW.h                               |  22 +-
 dwtools/DataModeler.cpp                     |   2 +-
 dwtools/Makefile                            |   6 +-
 dwtools/Sound_extensions.cpp                |  63 ++-
 dwtools/Sound_extensions.h                  |   2 +
 dwtools/Table_extensions.cpp                |   2 +-
 dwtools/TextGrid_and_DurationTier.cpp       | 100 ++++
 dwtools/TextGrid_and_DurationTier.h         |  33 ++
 dwtools/TextGrid_extensions.cpp             |   8 +
 dwtools/manual_dwtools.cpp                  | 116 +++-
 dwtools/praat_DataModeler_init.cpp          |  15 +-
 dwtools/praat_David_init.cpp                |  55 +-
 dwtools/praat_KlattGrid_init.cpp            |   6 +-
 external/gsl/gsl_rng__file.c                |   4 +-
 fon/FunctionEditor.h                        |   6 +-
 fon/RealTier.cpp                            |   3 +-
 fon/Sound_and_Spectrum.cpp                  |  45 +-
 fon/Sound_to_Intensity.cpp                  |  37 +-
 fon/Sound_to_Intensity.h                    |  10 +-
 fon/TimeSoundEditor.cpp                     |   9 +-
 fon/manual_Script.cpp                       | 266 ++++++---
 fon/manual_glossary.cpp                     |   2 +-
 fon/manual_pitch.cpp                        |   6 +-
 fon/manual_programming.cpp                  |  15 +-
 fon/manual_tutorials.cpp                    |  10 +-
 fon/praat_Fon.cpp                           |  44 +-
 fon/praat_Matrix.cpp                        |   9 +-
 fon/praat_Sound.cpp                         |  26 +-
 fon/praat_TextGrid_init.cpp                 |  36 +-
 fon/praat_Tiers.cpp                         |  53 +-
 gram/praat_gram.cpp                         |  16 +-
 kar/UnicodeData.h                           |   4 +
 kar/longchar.cpp                            |   2 +-
 main/Makefile                               |   4 +-
 main/praat.plist                            |   2 +-
 makefiles/makefile.defs.linux.barren        |   6 +-
 makefiles/makefile.defs.linux.nogui         |  25 +
 num/NUMrandom.cpp                           |   2 +-
 stat/Regression.cpp                         |  14 +-
 stat/Table.cpp                              |   2 +-
 stat/praat_Stat.cpp                         |   9 +-
 sys/Formula.cpp                             | 628 ++++++++++++++++-----
 sys/Formula.h                               |  23 +-
 sys/Graphics.cpp                            |   2 +
 sys/Graphics.h                              |   6 +-
 sys/GraphicsP.h                             |  27 +-
 sys/GraphicsScreen.cpp                      |  77 +--
 sys/Graphics_colour.cpp                     |  10 +-
 sys/Graphics_linesAndAreas.cpp              |  26 +-
 sys/Graphics_mouse.cpp                      |   4 +-
 sys/Graphics_text.cpp                       | 824 +++++++++++-----------------
 sys/Gui.h                                   |  29 +-
 sys/Interpreter.cpp                         |  22 +-
 sys/Interpreter.h                           |  10 +-
 sys/Makefile                                |   2 +-
 sys/MelderThread.h                          |   4 +-
 sys/Picture.cpp                             |   2 +-
 sys/Tensor.h                                |  32 ++
 sys/melder.h                                |   2 +-
 sys/melder_audio.cpp                        |   2 +-
 sys/melder_debug.cpp                        |   6 +-
 sys/oo.h                                    |   4 +-
 sys/praat.cpp                               |   6 +-
 sys/praat.h                                 |  11 +
 sys/praat_version.h                         |   8 +-
 sys/sendpraat.c                             |   8 +-
 test/script/vectors.praat                   |   8 +
 test/sys/graphicsTextSpeed.praat            |   9 +
 test/sys/object.praat                       |  35 ++
 90 files changed, 2591 insertions(+), 1526 deletions(-)

diff --git a/EEG/praat_EEG.cpp b/EEG/praat_EEG.cpp
index e32961d..688106e 100644
--- a/EEG/praat_EEG.cpp
+++ b/EEG/praat_EEG.cpp
@@ -1,6 +1,6 @@
 /* praat_EEG.cpp
  *
- * Copyright (C) 2011-2012,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 2011-2012,2013,2014,2015,2016,2017 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
@@ -43,12 +43,10 @@ static void cb_EEGWindow_publication (Editor /* editor */, autoDaata publication
 		praat_updateSelection ();
 		if (isaSpectralSlice) {
 			int IOBJECT;
-			LOOP {
-				iam_LOOP (Spectrum);
-				autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
-				praat_installEditor (editor2.get(), IOBJECT);
-				editor2.releaseToUser();
-			}
+			FIND_ONE_WITH_IOBJECT (Spectrum)
+			autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
+			praat_installEditor (editor2.get(), IOBJECT);
+			editor2.releaseToUser();
 		}
 	} catch (MelderError) {
 		Melder_flushError ();
@@ -56,14 +54,13 @@ static void cb_EEGWindow_publication (Editor /* editor */, autoDaata publication
 }
 DIRECT (WINDOW_EEG_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit an EEG from batch.");
-	LOOP {
-		iam_LOOP (EEG);
+	FIND_ONE_WITH_IOBJECT (EEG)
 		autoEEGWindow editor = EEGWindow_create (ID_AND_FULL_NAME, me);
 		Editor_setPublicationCallback (editor.get(), cb_EEGWindow_publication);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Query
 
@@ -319,12 +316,10 @@ static void cb_ERPWindow_publication (Editor /* editor */, autoDaata publication
 		praat_updateSelection ();
 		if (isaSpectralSlice) {
 			int IOBJECT;
-			LOOP {
-				iam_LOOP (Spectrum);
-				autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
-				praat_installEditor (editor2.get(), IOBJECT);
-				editor2.releaseToUser();
-			}
+			FIND_ONE_WITH_IOBJECT (Spectrum)
+			autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
+			praat_installEditor (editor2.get(), IOBJECT);
+			editor2.releaseToUser();
 		}
 	} catch (MelderError) {
 		Melder_flushError ();
@@ -332,14 +327,13 @@ static void cb_ERPWindow_publication (Editor /* editor */, autoDaata publication
 }
 DIRECT (WINDOW_ERP_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit an ERP from batch.");
-	LOOP {
-		iam_LOOP (ERP);
+	FIND_ONE_WITH_IOBJECT (ERP)
 		autoERPWindow editor = ERPWindow_create (ID_AND_FULL_NAME, me);
 		Editor_setPublicationCallback (editor.get(), cb_ERPWindow_publication);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Tabulate
 
diff --git a/FFNet/Makefile b/FFNet/Makefile
index a6cfcdf..515adf9 100644
--- a/FFNet/Makefile
+++ b/FFNet/Makefile
@@ -1,9 +1,9 @@
 # Makefile of the library "FFNet"
-# David Weenink, 24 May 2016
+# David Weenink, 2 June 2017
 
 include ../makefile.defs
 
-CPPFLAGS = -I ../num -I ../dwtools -I ../gram -I ../fon -I ../sys -I ../dwsys -I ../stat
+CPPFLAGS = -I ../num -I ../kar -I ../dwtools -I ../gram -I ../fon -I ../sys -I ../dwsys -I ../stat
 
 OBJECTS = FFNet.o \
 	FFNet_Eigen.o FFNet_Matrix.o FFNet_PatternList.o \
diff --git a/LPC/LPC_to_Spectrum.cpp b/LPC/LPC_to_Spectrum.cpp
index c3883b7..2f26e16 100644
--- a/LPC/LPC_to_Spectrum.cpp
+++ b/LPC/LPC_to_Spectrum.cpp
@@ -18,7 +18,7 @@
 
 /*
  djmw 20020529 GPL header
- djmw 20020529 Changed NUMrealft to NUMforwardRealFastFourierTransform_f
+ djmw 20020529 Changed NUMrealft to NUMforwardRealFastFourierTransform
  djmw 20030708 Added NUM2.h
  djmw 20080122 float -> double
 */
diff --git a/LPC/Makefile b/LPC/Makefile
index 3fa4da5..2320ac0 100644
--- a/LPC/Makefile
+++ b/LPC/Makefile
@@ -1,9 +1,9 @@
 # Makefile of the library "LPC"
-# David Weenink, 20130826
+# David Weenink, 20170602
 
 include ../makefile.defs
 
-CPPFLAGS = -I ../num -I ../dwtools -I ../fon -I ../sys -I ../dwsys -I ../stat
+CPPFLAGS = -I ../num -I ../kar -I ../dwtools -I ../fon -I ../sys -I ../dwsys -I ../stat
 
 OBJECTS = Cepstrum.o Cepstrumc.o Cepstrum_and_Spectrum.o \
 	Cepstrogram.o \
diff --git a/README.md b/README.md
index db3ab5d..f5cedb8 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,18 @@ you may have to edit the library names in the makefile (you may need pthread, gt
 pangoft2-1.0, gdk_pixbuf-2.0, m, pangocairo-1.0, cairo, gio-2.0, pango-1.0, freetype, fontconfig, gobject-2.0,
 gmodule-2.0, gthread-2.0, rt, glib-2.0).
 
+When compiling Praat for use as a server for commands from your web pages, you may not need sound or a GUI. Do
+
+    cp makefiles/makefile.defs.linux.nogui ./makefile.defs
+
+which creates the executable `praat_nogui`. If you don't need graphics (.e.g PNG files) either
+(i.e. you need only Praat's computation), you can create an even lighter edition:
+
+    cp makefiles/makefile.defs.linux.barren ./makefile.defs
+
+which creates the executable `praat_barren`. Then type `make` to build the program. If your Unix isn’t Linux,
+you may have to edit the library names in the makefile.
+
 ## 2. Binary executables
 
 While the [Praat website](http://www.praat.org) contains the latest executable for all platforms that we support
diff --git a/artsynth/Makefile b/artsynth/Makefile
index 059d82d..48afe0f 100644
--- a/artsynth/Makefile
+++ b/artsynth/Makefile
@@ -1,9 +1,9 @@
 # Makefile of the library "artsynth"
-# Paul Boersma, 24 August 2013
+# Paul Boersma, 2 June 2017
 
 include ../makefile.defs
 
-CPPFLAGS = -I ../num -I ../sys -I ../fon -I ../stat
+CPPFLAGS = -I ../num -I ../kar -I ../sys -I ../fon -I ../stat
 
 OBJECTS = Speaker.o Articulation.o Artword.o \
      Art_Speaker.o Art_Speaker_to_VocalTract.o Artword_Speaker.o Artword_Speaker_Sound.o \
diff --git a/artsynth/praat_Artsynth.cpp b/artsynth/praat_Artsynth.cpp
index 799c561..bfb4f32 100644
--- a/artsynth/praat_Artsynth.cpp
+++ b/artsynth/praat_Artsynth.cpp
@@ -1,6 +1,6 @@
 /* praat_Artsynth.cpp
  *
- * Copyright (C) 1992-2012,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2015,2016,2017 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
@@ -80,13 +80,12 @@ DO
 
 DIRECT (WINDOW_Artword_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit an Artword from batch.");
-	WHERE (SELECTED) {
-		iam_LOOP (Artword);
+	FIND_ONE_WITH_IOBJECT (Artword)
 		autoArtwordEditor editor = ArtwordEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 FORM (REAL_Artword_getTarget, U"Get one Artword target", nullptr) {
 	REALVAR (time, U"Time (seconds)", U"0.0")
diff --git a/contrib/ola/Makefile b/contrib/ola/Makefile
index 2c1d666..097da2f 100644
--- a/contrib/ola/Makefile
+++ b/contrib/ola/Makefile
@@ -1,10 +1,10 @@
 # Makefile of the library "contrib/ola"
 # Ola So"der 19 January 2008
-# Paul Boersma, 22 October 2016
+# Paul Boersma, 2 June 2017
 
 include ../../makefile.defs
 
-CPPFLAGS = -I ../../sys -I ../../FFNet -I ../../dwtools -I ../../fon -I ../../dwsys -I ../../stat -I ../../num -I ../../external/gsl -D_DEBUG -D_REENTRANT
+CPPFLAGS = -I ../../sys -I ../../kar -I ../../FFNet -I ../../dwtools -I ../../fon -I ../../dwsys -I ../../stat -I ../../num -I ../../external/gsl -D_DEBUG -D_REENTRANT
 
 OBJECTS = KNN.o \
    KNN_threads.o Pattern_to_Categories_cluster.o KNN_prune.o FeatureWeights.o praat_contrib_Ola_KNN.o manual_KNN.o
diff --git a/dwsys/DoublyLinkedList.cpp b/dwsys/DoublyLinkedList.cpp
index f27ab7b..026e467 100644
--- a/dwsys/DoublyLinkedList.cpp
+++ b/dwsys/DoublyLinkedList.cpp
@@ -124,7 +124,7 @@ void DoublyLinkedList_remove (DoublyLinkedList me, DoublyLinkedNode node) {
 		node -> next -> prev = node -> prev;
 	}
 	forget (node);
-	my numberOfNodes++;
+	my numberOfNodes--;
 }
 
 // Preconditions:
diff --git a/dwsys/Makefile b/dwsys/Makefile
index b6e35ce..e6d3022 100644
--- a/dwsys/Makefile
+++ b/dwsys/Makefile
@@ -1,5 +1,5 @@
 # makefile for library "dwsys".
-# David Weenink 20130826
+# David Weenink 20170531
 
 include ../makefile.defs
 
@@ -10,7 +10,7 @@ all: libdwsys.a
 OBJECTS = Collection_extensions.o Command.o \
 	DoublyLinkedList.o Eigen.o FileInMemory.o Graphics_extensions.o Index.o \
 	NUM2.o NUMhuber.o NUMlapack.o NUMmachar.o \
-	NUMf2c.o NUMcblas.o NUMclapack.o NUMfft_d.o NUMsort2.o \
+	NUMf2c.o NUMcblas.o NUMclapack.o NUMcomplex.o NUMfft_d.o NUMsort2.o \
 	NUMmathlib.o NUMstring.o \
 	Permutation.o Permutation_and_Index.o \
 	regularExp.o SimpleVector.o Simple_extensions.o \
diff --git a/dwsys/NUM2.cpp b/dwsys/NUM2.cpp
index 697555c..8f79ba1 100644
--- a/dwsys/NUM2.cpp
+++ b/dwsys/NUM2.cpp
@@ -63,12 +63,11 @@
  djmw 20140318 NUMvector_avevar now returns variance instead of sigma^2
 */
 
-#include <vector>
 #include "SVD.h"
 #include "Eigen.h"
 #include "NUMclapack.h"
 #ifndef _NUM_h_
-#include "NUM.h"
+	#include "NUM.h"
 #endif
 #include "NUM2.h"
 #include "NUMmachar.h"
@@ -92,7 +91,6 @@
 #define MAX(m,n) ((m) > (n) ? (m) : (n))
 #define MIN(m,n) ((m) < (n) ? (m) : (n))
 #define SIGN(a,b) ((b < 0) ? -fabs(a) : fabs(a))
-using namespace std;
 
 struct pdf1_struct {
 	double p;
@@ -520,27 +518,6 @@ int NUMstrcmp (const char *s1, const char *s2) {
 	}
 }
 
-void NUMlocate_f (float *xx, long n, float x, long *index) {
-	long ju = n + 1, jm, jl = 0;
-	int ascend = xx[n] >= xx[1];
-
-	while (ju - jl > 1) {
-		jm = (ju + jl) / 2;
-		if ( (x >= xx[jm]) == ascend) {
-			jl = jm;
-		} else {
-			ju = jm;
-		}
-	}
-	if (x == xx[1]) {
-		*index = 1;
-	} else if (x == xx[n]) {
-		*index = n - 1;
-	} else {
-		*index = jl;
-	}
-}
-
 void NUMlocate (double *xx, long n, double x, long *index) {
 	long ju = n + 1, jm, jl = 0;
 	int ascend = xx[n] >= xx[1];
@@ -3096,7 +3073,7 @@ TryAgain:
 
 			#else /* USE STIRLING */
 			/* The "#define Stirling" above corresponds to the first five
-			* terms in asymptoic formula for
+			* terms in asymptotic formula for
 			* log Gamma (y) - (y-0.5)log(y) + y - 0.5 log(2*pi);
 			* See Abramowitz and Stegun, eq 6.1.40
 			*/
@@ -3113,7 +3090,7 @@ TryAgain:
 			* O(1), ranging 0 to -10 or so, while the Stirling
 			* correction is typically O(10^{-5}) ...setting the
 			* correction to zero gives about a 2% performance boost;
-			* might as well keep it just to be pendantic.  */
+			* might as well keep it just to be pedantic.  */
 
 			{
 				double x1 = ix + 1.0;
@@ -3141,6 +3118,14 @@ Finish:
   	return (flipped) ? (n - ix) : ix;
 }
 
+double NUMrandomBinomial_real (double p, long n) {
+	if (p < 0.0 || p > 1.0 || n < 0) {
+		return NUMundefined;
+	} else {
+		return (double) NUMrandomBinomial (p, n);
+	}
+}
+
 void NUMlngamma_complex (double zr, double zi, double *lnr, double *arg) {
 	double ln_re = NUMundefined, ln_arg = NUMundefined;
 	gsl_sf_result gsl_lnr, gsl_arg;
diff --git a/dwsys/NUM2.h b/dwsys/NUM2.h
index 1463c84..464584e 100644
--- a/dwsys/NUM2.h
+++ b/dwsys/NUM2.h
@@ -2,7 +2,7 @@
 #define _NUM2_h_
 /* NUM2.h
  *
- * Copyright (C) 1997-2016 David Weenink
+ * Copyright (C) 1997-2017 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -430,7 +430,6 @@ void NUMrank (long n, T *a) {
 
 void NUMrankColumns (double **m, long rb, long re, long cb, long ce);
 
-void NUMlocate_f (float *xx, long n, float x, long *index);
 void NUMlocate (double *xx, long n, double x, long *index);
 /*
 	Given an array xx[1..n], and given a value x, returns a value index
@@ -452,7 +451,6 @@ int NUMjacobi (float **a, long n, float d[], float **v, long *nrot);
 	`nrot' returns the number of Jacobi rotations that were required.
  */
 
-void NUMtred2_f (float **a, long n, float d[], float e[]);
 void NUMtred2 (double **a, long n, double d[], double e[]);
 /*
 	Householder reduction of a real, symmetric matrix a[1..n][1..n]. On output,
@@ -461,7 +459,6 @@ void NUMtred2 (double **a, long n, double d[], double e[]);
 	the off-diagonal elements, with e[1] = 0.
 */
 
-int NUMtqli_f (float d[], float e[], long n, float **z);
 int NUMtqli (double d[], double e[], long n, double **z);
 /*
 	QL algorithm with implicit shifts, to determine the (sorted) eigenvalues
@@ -481,8 +478,6 @@ int NUMtqli (double d[], double e[], long n, double **z);
 */
 
 int NUMgaussj (double **a, long n, double **b, long m);
-
-int NUMgaussj_f (float **a, long n);
 /*
 	Calculate inverse of square matrix a[1..n][1..n] (in-place).
 	Method: Gauss-Jordan elimination with full pivoting.
@@ -509,7 +504,6 @@ int NUMsvbksb (double **u, double w[], double **v, long m, long n, double b[], d
 */
 
 int NUMludcmp (double **a, long n, long *indx, double *d);
-int NUMludcmp_f (float **a, long n, long *indx, float *d);
 /*	Given a matrix a[1..n][1..n], this routine replaces it by the
 	LU decomposition of a rowwise permutation of itself.
 	a	: matrix [1..n][1..n]
@@ -520,7 +514,7 @@ int NUMludcmp_f (float **a, long n, long *indx, float *d);
 		even/odd.
 */
 
-int NUMcholeskyDecomposition(double **a, long n, double d[]);
+int NUMcholeskyDecomposition (double **a, long n, double d[]);
 /*
 	Cholesky decomposition of a symmetric positive definite matrix.
 */
@@ -1147,13 +1141,6 @@ double NUMminimize_brent (double (*f) (double x, void *closure), double a, doubl
 
 /********************** fft ******************************************/
 
-struct structNUMfft_Table_f
-{
-  long n; /* Data length */
-  float *trigcache;
-  long *splitcache;
-};
-
 struct structNUMfft_Table
 {
   long n;
@@ -1161,10 +1148,8 @@ struct structNUMfft_Table
   long *splitcache;
 };
 
-typedef struct structNUMfft_Table_f *NUMfft_Table_f;
 typedef struct structNUMfft_Table *NUMfft_Table;
 
-void NUMfft_Table_init_f (NUMfft_Table_f table, long n);
 void NUMfft_Table_init (NUMfft_Table table, long n);
 /*
 	n : data size
@@ -1182,7 +1167,6 @@ struct autoNUMfft_Table : public structNUMfft_Table {
         }
 };
 
-void NUMfft_forward_f (NUMfft_Table_f table, float *data);
 void NUMfft_forward (NUMfft_Table table, double *data);
 /*
 	Function:
@@ -1229,7 +1213,6 @@ void NUMfft_forward (NUMfft_Table table, double *data);
              sequence by n.
 */
 
-void NUMfft_backward_f (NUMfft_Table_f table, float *data);
 void NUMfft_backward (NUMfft_Table table, double *data);
 /*
 	Function:
@@ -1272,7 +1255,6 @@ void NUMfft_backward (NUMfft_Table table, double *data);
 
 /**** Compatibility with NR fft's */
 
-void NUMforwardRealFastFourierTransform_f (float  *data, long n);
 void NUMforwardRealFastFourierTransform (double  *data, long n);
 /*
 	Function:
@@ -1287,7 +1269,6 @@ void NUMforwardRealFastFourierTransform (double  *data, long n);
 		data [2] contains real valued last component (Nyquist frequency)
 		data [3..n] odd index : real part; even index: imaginary part of DFT.
 */
-void NUMreverseRealFastFourierTransform_f (float  *data, long n);
 void NUMreverseRealFastFourierTransform (double  *data, long n);
 /*
 	Function:
@@ -1300,7 +1281,6 @@ void NUMreverseRealFastFourierTransform (double  *data, long n);
 		data [2] contains real valued last component (Nyquist frequency)
 		data [3..n] odd index : real part; even index: imaginary part of DFT.
 */
-void NUMrealft_f (float *data, long n, int direction);    /* Please stop using. */
 void NUMrealft (double *data, long n, int direction);
 
 long NUMgetIndexFromProbability (double *probs, long nprobs, double p);
@@ -1374,6 +1354,7 @@ void NUMlineFit_LS (double *x, double *y, long numberOfPoints, double *m, double
    Additional polishing for GSL coding standards by Brian Gough.  */
 
 long NUMrandomBinomial (double p, long n);
+double NUMrandomBinomial_real (double p, long n);
 
 // IEEE: Programs for digital signal processing section 4.3 LPTRN (modfied)
 
diff --git a/dwsys/NUMcomplex.cpp b/dwsys/NUMcomplex.cpp
new file mode 100644
index 0000000..d72c332
--- /dev/null
+++ b/dwsys/NUMcomplex.cpp
@@ -0,0 +1,206 @@
+/* NUMcomplex.cpp
+ *
+ * Copyright (C) 2017 David Weenink
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cmath>
+#include <complex>
+#include "NUMcomplex.h"
+
+// The following code was translated from fortran into c++ by David weenink.
+// The fortran code is from the article of Eric Kostlan and Dmitry Gokhman, A program for calculating the incomplete 
+//		gamma function. Technical report, Dept. of Mathematics, Univ. of California, Berkeley, 1987.
+
+static double norm1 (std::complex<double> *x) {
+	return fabs (real(*x)) + fabs (imag(*x));
+}
+
+static void term (std::complex<double> *alpha, std::complex<double> *x, long i, std::complex<double> *p, std::complex<double> *q) {
+// Calculate p*q = (-1)^i (1-x^(alpha+i))/(alpha+i)i! 
+	std::complex<double> zero = 0.0;
+	double tol = 3e-7, xlim = 39.0, di = i;
+
+	if (i == 0) {
+		*q = 1.0;
+	}
+	std::complex<double> alphai = *alpha + di;
+	if (*x == zero) {
+		*p = 1.0 / alphai;
+		if (i != 0) {
+			*q /= -di;
+		}
+		return;
+	}
+	std::complex<double> cdlx = log (*x);
+	// If (1-x**alphai) = -x**alphai,
+	// then change the inductive scheme to avoid overflow.
+	if (real (alphai * cdlx) > xlim && i != 0) {
+			*p *= (alphai - 1.0) / alphai;
+			*q *= - *x / di;
+		return;
+	}
+	if (norm1 (& alphai) > tol) {
+		*p = (1.0 - std::pow (*x, alphai)) / alphai;
+	} else {
+		*p = -cdlx * (1.0 + cdlx * alphai * 0.5);
+	}
+	if (i == 0) {
+		*q /= - di;
+	}
+}
+
+static void cdhs (std::complex<double> *alpha, std::complex<double> *x, std::complex<double> *result) {
+	std::complex<double> zero (0.0,0.0);
+	std::complex<double> q0 = 1.0, q1 = 1.0, p0 = *x, p1 = *x + 1.0 - *alpha, r0;
+	double tol1 = 1e10, tol2 = 1e-10, error = 1e-18;
+	for (long i = 1; i <= 100000; i++) {
+		double di = i;
+		if (p0 != zero && q0 != zero && q1 != zero) {
+			r0 = p0 / q0;
+			*result = p1 / q1;
+			std::complex<double> r0mr1 = r0 - *result;
+			if (norm1 (& r0mr1) < norm1 (result) * error) {
+				return;
+			}
+			// renormalize to avoid underflow or overflow
+			if (norm1 (& p0) > tol1 || norm1 (& p0) < tol2 || norm1 (& q0) > tol1 || norm1 (& q0) < tol2) {
+				std::complex<double> factor = p0 * q0;
+				p0 /= factor;
+				q0 /= factor;
+				p1 /= factor;
+				q1 /= factor;
+			}
+			p0 = *x * p1 + di * p0; // y[k+2] = x * y[k+1] + (k+2)/2 * y[k] with k even
+			q0 = *x * q1 + di * q0;
+			p1 = p0 + (di + 1.0 - *alpha) * p1; // y[k+2] = y[k+1] + ((k+3)/2 - alpha) * y[k] with k odd
+			q1 = q0 + (di + 1.0 - *alpha) * q1;
+		}
+	}
+	// We should not come here at all!
+	*result = 0.5 * (r0 + *result);
+}
+
+static void cdh (std::complex<double> *alpha, std::complex<double> *x, std::complex<double> *result) {
+	std::complex<double> one (1.0, 0.0);
+	long n = (long) real (*alpha - *x);
+	if (n > 0) {
+		std::complex<double> cn = n + 1;
+		std::complex<double> alpha1 = *alpha - cn;
+		std::complex<double> term = one / *x;
+		std::complex<double> sum = term;
+		for (long i = 1; i <= n; i++) {
+			cn = n - i + 1;
+			term *= (alpha1 + cn) / *x;
+			sum += term;
+		}
+		cdhs (&alpha1, x, result);
+		sum += term * alpha1 / *result;
+		*result = one / sum;
+	} else {
+		cdhs (alpha, x, result);
+	}
+}
+
+// Gamma[alpha,x] = integral{x, infty, t^(alpha-1)exp(-t)dt}, Gamma[alpha]= Gamma[alpha,0]
+void NUMincompleteGammaFunction (double alpha_re, double alpha_im, double x_re, double x_im, double *result_re, double *result_im) {
+	std::complex<double> alpha (alpha_re, alpha_im), x (x_re, x_im), result;
+	double xlim = 1.0;
+	long ibuf = 34;
+	std::complex<double> re = 0.36787944117144232, one = 1.0, p, q, r;
+	if (norm1 (& x) < xlim || real (x) < 0.0 && fabs (imag (x)) < xlim) {
+		cdh (& alpha, & one, & r);
+		result = re / r;
+		long ilim = real (x / re);
+		for (long i = 0; i <= ibuf - ilim; i++) {
+			term (& alpha, & x, i, & p, & q);
+			result += p * q;
+		}
+	} else {
+		cdh (& alpha, & x, & r);
+		result = exp (-x + alpha * log (x)) / r;
+	}
+	if (result_re) {
+		*result_re = result.real();
+	}
+	if (result_im) {
+		*result_im = result.imag();
+	}
+}
+
+// End of translated fortran code
+
+/* 
+* We have to scale the amplitude "a" of the gammatone such that the response is 0 dB at the peak (w=w0);
+* 
+* To calculate the spectrum of the finite gammatone 
+* 		g(t) = t^(n-1)*exp(-b*t)cos(w0*t+phi) for 0 < t <= T
+* 		g(t) = 0 for t > T
+* we can write
+* 		g(t)= t^(n-1)*exp(-b*t)(exp(I*(w0*t+phi))+exp(-I*(w0*t+phi)))/2
+* 			= (gp(t)+gm(t))/2, where
+* 		gp(t) = t^(n-1)*exp(-b*t + I*(w0*t+phi))
+* 		gm(t) = t^(n-1)*exp(-b*t - I*(w0*t+phi))
+* 
+* Laplace[g(t)]= Laplace[g(t)]-Laplace[g(t+T)],where
+* 		g(t+T) = g2(t) = (t+T)^(n-1)*exp(-b*(t+T))cos(w0*(t+T)+phi) for t>0
+* 
+*		g2(t) = (t+T)^(n-1)*(exp(-b*(t+T)+I(w0*(t+T)+phi) + exp(-b*(t+T)-I(w0*(t+T)+phi)))/2
+* 			  = (exp(-b*T+I*w0*T+I*phi)(t+T)^(n-1)exp(-b*t+I*w0*t) + exp(-b*T-I*w0*T-I*phi)(t+T)^(n-1)exp(-b*t-I*w0*t))/2
+* 			  = (gpT(t)+gmT(t))/2, where
+* 		gpT(t)= exp(-b*T+I*w0*T+I*phi) (t+T)^(n-1) exp(-b*t+I*w0*t)
+* 		gmT(t)= exp(-b*T-I*w0*T-I*phi) (t+T)^(n-1) exp(-b*t-I*w0*t)
+* 
+* Fp(w)  = Laplace[gp(t), s=Iw] = exp( I*phi) (b+I(w-w0))^-n Gamma[n]
+* Fm(w)  = Laplace[gm(t), s=Iw] = exp(-I*phi) (b+I(w+w0))^-n Gamma[n]
+* FpT(w) = Laplace[gpT(t),s=Iw] = exp( I*phi) exp(I*w*T) (b+I(w-w0))^-n Gamma[n, b*T+I(w-w0)*T]
+* FmT(w) = Laplace[gmT(t),s=Iw] = exp(-I*phi) exp(I*w*T) (b+I(w+w0))^-n Gamma[n, b*T+I(w+w0)*T]
+* F[g(t)] = (Fp(w) + Fm(w) - FpT(w) - FmT(w)) / 2
+* 
+* At resonance w=w0:
+* 	F(w0) = (Fp(w0) + Fm(w0) - FpT(w0) - FmT(w0)) / 2 =
+*		  = (exp(I*phi) (b)^-n Gamma[n] + exp(-I*phi) (b+I*2*w0)^-n Gamma[n] -
+* 			exp(I*phi) exp(I*w0*T) (b)^-n Gamma[n, b*T] -
+* 			exp(-I*phi) exp(I*w0*T) (b+I*2*w0)^-n Gamma[n, b*T+I*2*w0)]) / 2
+* 		  = b^-n(exp(I*phi) Gamma[n] + exp(-I*phi) (1+I*2*w0/b)^-n Gamma[n] -
+* 			exp(I*phi) exp(I*w0*T) Gamma[n, b*T0] - exp(-I*phi) exp(I*w0*T) (1+I*2*w0/b)^-n Gamma[n, b*T+I*2*w0*T)]) / 2
+* 
+* (x+I*y)^-n = (r*exp(I theta))^-n, where r = sqrt(x^2+y^2) and theta = ArcTan (y/x)
+* (1+I*a)^-n = (1+a^2)^(-n/2) exp(-I*n*theta)
+*/
+void gammaToneFilterResponseAtResonance (double centre_frequency, double bandwidth, long gamma, double initialPhase, double t0, double *response_re, double *response_im) {
+	
+	double b = NUM2pi * bandwidth, w0 = NUM2pi * centre_frequency, theta = atan (2.0 * centre_frequency / bandwidth);
+	double gamma_n = exp (NUMlnGamma (gamma)), bpow = pow (b, -gamma);
+	std::complex<double> expiphi (cos (initialPhase), sin(initialPhase)), expmiphi = conj (expiphi);
+	std::complex<double> expnitheta (cos (gamma * theta), - sin(gamma * theta));
+	std::complex<double> expiw0t0 (cos (w0 * t0), sin (w0 * t0));
+	std::complex<double> peak = expnitheta * pow (1.0 + 4.0 * (w0 / b) * (w0 / b), - 0.5 * gamma);
+	double result1_re, result1_im, result2_re, result2_im;
+	NUMincompleteGammaFunction (gamma, 0.0, b * t0, 0.0,           & result1_re, & result1_im);
+	NUMincompleteGammaFunction (gamma, 0.0, b * t0, 2.0 * w0 * t0, & result2_re, & result2_im);
+	std::complex<double> result1 (result1_re, result1_im), result2 (result2_re, result2_im);
+	std::complex<double> filterResponseAtResonance = 0.5 * bpow * ((expiphi + expmiphi * peak) * gamma_n -
+		expiw0t0 * (expiphi * result1 + expmiphi * peak * result2));
+	if (response_re) {
+		*response_re = filterResponseAtResonance.real ();
+	}
+	if (response_im) {
+		*response_im = filterResponseAtResonance.imag ();
+	}
+}
+
+
+/* End of file NUMcomplex.cpp */
diff --git a/dwsys/NUMcomplex.h b/dwsys/NUMcomplex.h
new file mode 100644
index 0000000..14db9b9
--- /dev/null
+++ b/dwsys/NUMcomplex.h
@@ -0,0 +1,31 @@
+#ifndef _NUMcomplex_h_
+#define _NUMcomplex_h_
+/* NUMcomplex.h
+ *
+ * Copyright (C) 2017 David Weenink
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "NUM2.h"
+
+/* Gamma[alpha,x] = integral{x, infty, t^(alpha-1)exp(-t)dt}, Gamma[alpha]= Gamma[alpha,0]
+ * alpha and x are complex numbers with Re(alpha) > 0
+ */
+void NUMincompleteGammaFunction (double alpha_re, double alpha_im, double x_re, double x_im, double *result_re, double *result_im);
+
+void gammaToneFilterResponseAtResonance (double centre_frequency, double bandwidth, long gamma, double initialPhase, double t0, double *response_re, double *response_im);
+
+#endif
+
diff --git a/dwsys/NUMlapack.cpp b/dwsys/NUMlapack.cpp
index f0c1bb0..9b57fb6 100644
--- a/dwsys/NUMlapack.cpp
+++ b/dwsys/NUMlapack.cpp
@@ -748,7 +748,7 @@ void NUMsvdcmp22 (double f, double g, double h, double *svmin, double *svmax,
 			a = 0.5 * (s + r);
 			*svmin = ha / a;
 			*svmax = fa * a;
-			t = mm == 0 ? (l == 0 ? SIGN (2, ft) * SIGN (1, gt) :
+			t = mm == 0 ? (l == 0 ? SIGN (2.0, ft) * SIGN (1.0, gt) :
 			               gt / SIGN (d, ft) + m / t) : (m / (s + t) + m / (r + l)) * (1 + a);
 			l = sqrt (t * t + 4);
 			crt = 2 / l;
@@ -772,15 +772,15 @@ void NUMsvdcmp22 (double f, double g, double h, double *svmin, double *svmax,
 	// Correct the signs of svmin and svmax
 
 	if (pmax == 1) {
-		tsign = SIGN (1, *csr) * SIGN (1, *csl) * SIGN (1, f);
+		tsign = SIGN (1.0, *csr) * SIGN (1.0, *csl) * SIGN (1.0, f);
 	} else if (pmax == 2) {
-		tsign = SIGN (1, *snr) * SIGN (1, *csl) * SIGN (1, g);
+		tsign = SIGN (1.0, *snr) * SIGN (1.0, *csl) * SIGN (1.0, g);
 	} else { /* pmax == 3 */
-		tsign = SIGN (1, *snr) * SIGN (1, *snl) * SIGN (1, h);
+		tsign = SIGN (1.0, *snr) * SIGN (1.0, *snl) * SIGN (1.0, h);
 	}
 
 	*svmax = SIGN (*svmax, tsign);
-	*svmin = SIGN (*svmin, tsign * SIGN (1, f) * SIGN (1, h));
+	*svmin = SIGN (*svmin, tsign * SIGN (1.0, f) * SIGN (1.0, h));
 }
 
 /* Bai & Demmel, page 1472 */
diff --git a/dwsys/NUMstring.cpp b/dwsys/NUMstring.cpp
index 4c64368..7e62a78 100644
--- a/dwsys/NUMstring.cpp
+++ b/dwsys/NUMstring.cpp
@@ -491,7 +491,7 @@ static void NUMlvector_getUniqueNumbers (long *numbers, long *p_numberOfElements
 	long numberOfMultiples = 0;
 	
 	numbers [1] = sorted [1];
-	long i = 2, numberOfUniques = 1;
+	long numberOfUniques = 1;
 	for (long i = 2; i = *p_numberOfElements; i++) {
 		if (sorted[i] != sorted[i - 1]) {
 			numbers [++numberOfUniques] = sorted[i];
diff --git a/dwtest/test_DTW.praat b/dwtest/test_DTW.praat
index d5d097f..75d304b 100644
--- a/dwtest/test_DTW.praat
+++ b/dwtest/test_DTW.praat
@@ -36,7 +36,7 @@ select dtw
 mat = To Matrix (distances)
 Remove
 select dtw
-To Matrix (cumm. distances)... 0.05 2/3 < slope < 3/2
+To Matrix (cum. distances)... 0.05 2/3 < slope < 3/2
 Remove
 select dtw
 plus s1
diff --git a/dwtest/test_Sound_draw_where.praat b/dwtest/test_Sound_draw_where.praat
index 5626051..3e838e1 100644
--- a/dwtest/test_Sound_draw_where.praat
+++ b/dwtest/test_Sound_draw_where.praat
@@ -5,76 +5,73 @@ Erase all
 
 printline Draw where... Begin tests
 # maximally steep
-printline   Top: sawtooth in black with parts above 0.5 in blue
+printline   Top: sawtooth in blue with parts above 0.5 in red
 s = Create Sound from formula... s Mono 0 1 44100 20*((x mod 0.1) - 0.05)
 ymin = -1.1
- ymax = 1.1
+ymax = 1.1
 Select outer viewport... 0 6 0 3
-Blue
+Red
 Draw where... 0 0.4 ymin ymax n Curve self>0.5
-Black
+Blue
 Draw where... 0 0.4 ymin ymax n Curve self<=0.5
 One mark left... 0.5 y y y
 Draw inner box
-Text top... n sawtooth in black with parts above 0.5 in blue
+Text top... n sawtooth in blue with parts above 0.5 in red
 Remove
 
 # corner cases
-printline   Second figure, interpolation between sample points:  three sample points, parts above 0.5 in blue
+printline   Second figure, interpolation between sample points:  three sample points, parts above 0.5 in red
 s =Create Sound from formula... s Mono 0 0.0003 10000 0
 Select outer viewport... 0 6 3 6
-Blue
-Formula... if col=1 then -1 else if col=2 then 1 else -1 fi fi
+Red
+Formula... if col=2 then -1 else 1 fi
 Draw where... 0 0 ymin ymax n Curve self>0.5
-Black
+Blue
 Draw where... 0 0 ymin ymax n Curve self<=0.5
 One mark left... 0.5 y y y
 
-Formula... if col=1 then 1 else if col=2 then -1 else 1 fi fi
-Blue
+Formula... if col=2 then 1 else -1 fi
+Red
 Draw where... 0 0 ymin ymax n Curve self>0.5
-
-Black
+Blue
 Draw where... 0 0 ymin ymax n Curve self<=0.5
 One mark left... 0.5 y y y
 
-# Compatibility with Draw... : only draw in blue
+# Compatibility with Draw... : only draw in red
 Formula... 0.6
-Blue
+Red
 Draw where... 0 0 ymin ymax n Curve 1
-
-Black
+Blue
 Draw where... 0 0 ymin ymax n Curve 0
-
 Draw inner box
-Text top... n  (2x) three sample points, parts above 0.5 in blue +  blue straight
+Text top... n  (2x) three sample points, parts above 0.5 in red + red line
 Remove
 
 
 # random, selection on amplitudes
-printline   Random amplitudes draw parts > 0.5 in blue
+printline   Random amplitudes: parts > 0.5 in red
 Select outer viewport... 0 6 6 9
 s = Create Sound from formula... s Mono 0 0.01 10000 randomUniform(-1,1)
-Blue
+Red
 Draw where... 0 0 ymin ymax n Curve self>0.5
-Black
+Blue
 Draw where... 0 0 ymin ymax n Curve self<=0.5
 One mark left... 0.5 y y y
 Draw inner box
-Text top... n Random amplitudes: parts > 0.5 in blue
+Text top... n Random amplitudes: parts > 0.5 in red
 
 # complementary selections in amplitude and time domain
-printline   Random amplitudes: parts > 0.5 in blue only in the first half of every 0.001 s
+printline   Random amplitudes: parts > 0.5 in red only in the first half of every 0.001 s
 Select outer viewport... 0 6 9 12
-Blue
+Red
 Draw where... 0.001 0.01 ymin ymax n Curve (self>0.5 and x mod 0.001 < 0.0005)
-Black
+Blue
 Draw where... 0.001 0.01 ymin ymax n Curve not (self>0.5 and x mod 0.001 < 0.0005)
 One mark left... 0.5 y y y
 Marks bottom every... 1 0.0005 n y y
 Marks bottom every... 1 0.001 y y y
 Draw inner box
-Text top... n Random amplitudes: parts > 0.5 blue only in the first half of every 0.001 s
+Text top... n Random amplitudes: parts > 0.5 red in the first half of every 0.001 s
 Remove
 printline Draw where... End tests
 
diff --git a/dwtest/test_gammatonefilter.praat b/dwtest/test_gammatonefilter.praat
new file mode 100644
index 0000000..fce9377
--- /dev/null
+++ b/dwtest/test_gammatonefilter.praat
@@ -0,0 +1,52 @@
+# test_gammatonefilter.praat
+# djmw 20170531
+
+for i to 5
+	puls [i] = Create Sound from formula: "puls", 1, 0, i * 0.2, 44100, "col=1"
+endfor
+
+appendInfoLine: "test_gammatonefilter.praat"
+appendInfoLine: tab$, "Filter pulses of duration i * 0.2"
+header$ = "F"+tab$+"Q"+tab$+"Peak(dB)"+tab$+"dF"
+output$ = header$ +newline$
+q = 0.1
+f = 1000
+
+for ifreq to 20
+	b = 100
+	f = 500 + (ifreq - 1) * 100
+	for i to 5
+		selectObject: puls [i]
+		s = Filter (gammatone): f, b
+		spec = To Spectrum: "yes"
+		be [i]  = Get band energy: f - 0.5 * b, f + 0.5 * b
+		removeObject: s, spec
+	endfor
+	for i from 2 to 5
+		;appendInfoLine: abs(be[i] -be [i-1]) / abs (be[i])
+		assert abs(be[i] -be [i-1]) / abs (be[i]) < 1e-4
+	endfor
+endfor
+
+for i to 5
+	removeObject: puls [i]
+endfor
+
+appendInfoLine: tab$, "Compare incomplete gamma with Gamma[n,z]"
+# values in the csv file were calculated by the Mathematica function 
+# N[Gamma[i, i + I i/j], 10]
+Read Table from comma-separated file: "incompleteGamma.csv"
+numberOfLines = Get number of rows
+eps = 1e-7
+for irow to numberOfLines
+	i = Get value: irow, "i"
+	j = Get value: irow, "j"
+	re = Get value: irow, "re"
+	im = Get value: irow, "im"
+	z$ = Get incomplete gamma: i, 0.0, i, i / j
+	pre = number (extractWord$ (z$, ""))
+	pim = number (extractWord$ (z$, " "))
+	;appendInfoLine: irow, " ", re, " ", im, " ", pre, " ", pim
+	assert abs((pre - re)/pre) < eps && abs ((pim - im)/pim) < eps; 'irow'
+endfor
+appendInfoLine: "test_gammatonefilter.praat OK"
diff --git a/dwtest/test_textGrid_and_DurationTier.praat b/dwtest/test_textGrid_and_DurationTier.praat
new file mode 100644
index 0000000..0a01254
--- /dev/null
+++ b/dwtest/test_textGrid_and_DurationTier.praat
@@ -0,0 +1,61 @@
+# test_TextGrid_and_DurationTier.praat
+
+# check the interface 
+
+# Create a TextGrid
+
+appendInfoLine: "test_TextGrid_and_DurationTier.praat"
+
+for i to 10
+	f = randomUniform (100, 400)
+	sound[i] = Create Sound as pure tone: string$ (f), 1, 0, 0.4, 44100, f, 0.2, 0.01, 0.01
+endfor
+
+selectObject: sound[1]
+for i from 2 to 10
+	plusObject: sound[i]
+endfor
+
+Concatenate recoverably
+sound = selected ("Sound")
+textgrid = selected ("TextGrid")
+
+for i to 10
+	removeObject: sound[i]
+endfor
+
+# some duration manipulations
+selectObject: sound
+duration = Get total duration
+durationTier = Create DurationTier: "chain", 0, duration
+
+selectObject: textgrid
+numberOfIntervals = Get number of intervals: 1
+
+deltaTime = 0.0000001
+for i to numberOfIntervals
+	if i mod 2 = 0
+		selectObject: textgrid
+		t1 = Get start time of interval: 1, i
+		t2 = Get end time of interval: 1, i
+		selectObject: durationTier
+		Add point: t1, 1
+		Add point: t1+deltaTime, 1.9
+		Add point: t2-deltaTime, 1.9
+		Add point: t2, 1
+	endif
+endfor
+
+selectObject: durationTier, textgrid
+textgrid2 = To TextGrid (scale times)
+
+selectObject: sound
+manipulation = To Manipulation: 0.01, 75, 600
+plusObject: durationTier
+Replace duration tier
+selectObject: manipulation
+sound2 = Get resynthesis (overlap-add)
+
+;removeObject: sound, sound2, textgrid, textgrid2, manipulation, durationTier
+
+appendInfoLine: "test_TextGrid_and_DurationTier.praat OK"
diff --git a/dwtools/DTW.cpp b/dwtools/DTW.cpp
index 04896d7..ffaa052 100644
--- a/dwtools/DTW.cpp
+++ b/dwtools/DTW.cpp
@@ -67,8 +67,8 @@ Thing_implement (DTW, Matrix, 2);
 
 void structDTW :: v_info () {
 	structDaata :: v_info ();
-	MelderInfo_writeLine (U"Domain prototype:", ymin, U" to ", ymax, U" (s).");   // ppgb: Wat is een domain prototype?
-	MelderInfo_writeLine (U"Domain candidate:", xmin, U" to ", xmax, U" (s).");   // ppgb: Wat is een domain candidate?
+	MelderInfo_writeLine (U"Domain prototype: ", ymin, U" to ", ymax, U" (s).");   // ppgb: Wat is een domain prototype?
+	MelderInfo_writeLine (U"Domain candidate: ", xmin, U" to ", xmax, U" (s).");   // ppgb: Wat is een domain candidate?
 	MelderInfo_writeLine (U"Number of frames prototype: ", ny);
 	MelderInfo_writeLine (U"Number of frames candidate: ", nx);
 	MelderInfo_writeLine (U"Path length (frames): ", pathLength);
@@ -82,11 +82,12 @@ void structDTW :: v_info () {
 	}
 }
 
+static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, double ymin,
+                              double ymax, bool garnish, bool inset);
 static void DTW_paintDistances_raw (DTW me, Graphics g, double xmin, double xmax, double ymin,
-                                    double ymax, double minimum, double maximum, int garnish, int inset);
-static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, int garnish, int inset);
+                                    double ymax, double minimum, double maximum, bool garnish, bool inset);
 static double _DTW_and_Sounds_getPartY (Graphics g, double dtw_part_x);
-static void DTW_findPath_special (DTW me, int matchStart, int matchEnd, int slope, autoMatrix *cummulativeDists);
+static void DTW_findPath_special (DTW me, int matchStart, int matchEnd, int slope, autoMatrix *cumulativeDists);
 /*
 	Two 'slope lines, lh and ll, start in the lower left corner, the upper/lower has the maximum/minimum allowed slope.
 	Two other lines, ru and rl, end in the upper-right corner. The upper/lower line have minimum/maximum slope.
@@ -183,20 +184,20 @@ void DTW_Path_recode (DTW me) {
 		/* 1. Starting point always at origin */
 		long nxymax = thy nx + thy ny + 2;
 		autoNUMvector<struct structPoint> xytimes (1, nxymax);
-		xytimes[1].x = my xmin;
-		xytimes[1].y = my ymin;
+		xytimes [1]. x = my xmin;
+		xytimes [1]. y = my ymin;
 		/* 2. next point lower left of first cell */
-		nsc_x = my path[1].x;
+		nsc_x = my path [1]. x;
 		ixp = nsc_x - 1;
-		xytimes[2].x = my x1 + (nsc_x - 1 - 0.5) * my dx;
-		nsc_y = my path[1].y;
+		xytimes [2]. x = my x1 + (nsc_x - 1 - 0.5) * my dx;
+		nsc_y = my path [1]. y;
 		iyp = nsc_y - 1;
-		xytimes[2].y = my y1 + (nsc_y - 1 - 0.5) * my dy;
+		xytimes [2]. y = my y1 + (nsc_y - 1 - 0.5) * my dy;
 		/* 3. follow all cells. implicit: my x1 - 0.5 * my dx > my xmin && my y1 - 0.5 * my dy > my ymin */
 		nxy = 2;
-		for (long j = 1; j <= my pathLength; j++) {
+		for (long j = 1; j <= my pathLength; j ++) {
 			long index; // where are we in the new path?
-			long ix = my path[j].x, iy = my path[j].y;
+			long ix = my path [j]. x, iy = my path [j]. y;
 			double xright = my x1 + (ix - 1 + 0.5) * my dx;
 			double x, y, f, ytop = my y1 + (iy - 1 + 0.5) * my dy;
 
@@ -205,9 +206,10 @@ void DTW_Path_recode (DTW me) {
 				if (yDirection) { // we came from vertical direction?
 					// We came from a vertical direction so this is the second horizontal cell in a row.
 					// The statement after this "if" updates nsc_x to 2.
-					nsc_x = 1; yDirection = false;
+					nsc_x = 1;
+					yDirection = false;
 				}
-				nsc_x++;
+				nsc_x ++;
 
 				if (nsc_y > 1 || nd > 1) {
 					// Previous segment was diagonal or vertical: modify intersection
@@ -219,16 +221,17 @@ void DTW_Path_recode (DTW me) {
 					index = nxy - 1;
 					if (nsc_x == 2) {
 						index = nxy;
-						nxy++;
+						nxy ++;
 					}
-					xytimes[index].x = x;
-					xytimes[index].y = y;
+					xytimes [index]. x = x;
+					xytimes [index]. y = y;
 				}
 				nd = 0;
 			} else if (ix == ixp) { // vertical
 				yDirection = true;
 				if (xDirection) {
-					nsc_y = 1; xDirection = false;
+					nsc_y = 1;
+					xDirection = false;
 				}
 				nsc_y++;
 
@@ -240,25 +243,26 @@ void DTW_Path_recode (DTW me) {
 					index = nxy - 1;
 					if (nsc_y == 2) {
 						index = nxy;
-						nxy++;
+						nxy ++;
 					}
-					xytimes[index].x = x;
-					xytimes[index].y = y;
+					xytimes [index]. x = x;
+					xytimes [index]. y = y;
 				}
 				nd = 0;
-			} else if (ix == (ixp + 1) && iy == (iyp + 1)) { // diagonal
+			} else if (ix == ixp + 1 && iy == iyp + 1) { // diagonal
 				nd++;
 				if (nd == 1) {
-					nxy++;
+					nxy ++;
 				}
 				nsc_x = nsc_y = 1;
 			} else {
 				Melder_throw (U"The path goes back in time.");
 			}
 			// update
-			xytimes[nxy].x = xright;
-			xytimes[nxy].y = ytop;
-			ixp = ix; iyp = iy;
+			xytimes [nxy]. x = xright;
+			xytimes [nxy]. y = ytop;
+			ixp = ix;
+			iyp = iy;
 		}
 
 		if (my xmax > xytimes[nxy].x || my ymax > xytimes[nxy].y) {
@@ -271,9 +275,9 @@ void DTW_Path_recode (DTW me) {
 		thy nxy = nxy;
 		thy yfromx = RealTier_create (my xmin, my xmax);
 		thy xfromy = RealTier_create (my ymin, my ymax);
-		for (long i = 1; i <= nxy; i++) {
-			RealTier_addPoint (thy yfromx.get(), xytimes[i].x, xytimes[i].y);
-			RealTier_addPoint (thy xfromy.get(), xytimes[i].y, xytimes[i].x);
+		for (long i = 1; i <= nxy; i ++) {
+			RealTier_addPoint (thy yfromx.get(), xytimes [i]. x, xytimes [i]. y);
+			RealTier_addPoint (thy xfromy.get(), xytimes [i]. y, xytimes [i]. x);
 		}
 		//DTW_Path_makeIndex (me, DTW_X);
 		//DTW_Path_makeIndex (me, DTW_Y);
@@ -299,19 +303,19 @@ void DTW_Path_recode (DTW me) {
 	*/
 
 	/* 1. Starting point always at origin */
-	thy xytimes[1].x = my xmin;
-	thy xytimes[1].y = my ymin;
-	nx = my path[1].x;
+	thy xytimes [1]. x = my xmin;
+	thy xytimes [1]. y = my ymin;
+	nx = my path [1]. x;
 	ixp = nx - 1;
-	thy xytimes[2].x = my x1 + (nx - 1 - 0.5) * my dx;
-	ny = my path[1].y;
+	thy xytimes [2]. x = my x1 + (nx - 1 - 0.5) * my dx;
+	ny = my path [1]. y;
 	iyp = ny - 1;
-	thy xytimes[2].y = my y1 + (ny - 1 - 0.5) * my dy;
+	thy xytimes [2]. y = my y1 + (ny - 1 - 0.5) * my dy;
 	// implicit: my x1 - 0.5 * my dx > my xmin && my y1 - 0.5 * my dy > my ymin
 	nxy = 2;
 	for (long j = 1; j <= my pathLength; j++) {
 		long index; // where are we in the new path?
-		long ix = my path[j].x, iy = my path[j].y;
+		long ix = my path [j]. x, iy = my path [j]. y;
 		double xright = my x1 + (ix - 1 + 0.5) * my dx;
 		double x, y, f, ytop = my y1 + (iy - 1 + 0.5) * my dy;
 
@@ -334,18 +338,19 @@ void DTW_Path_recode (DTW me) {
 				index = nxy - 1;
 				if (nx == 2) {
 					index = nxy;
-					nxy++;
+					nxy ++;
 				}
-				thy xytimes[index].x = x;
-				thy xytimes[index].y = y;
+				thy xytimes [index]. x = x;
+				thy xytimes [index]. y = y;
 			}
 			nd = 0;
 		} else if (ix == ixp) { // vertical
 			isv = 1;
 			if (ish) {
-				ny = 1; ish = 0;
+				ny = 1;
+				ish = 0;
 			}
-			ny++;
+			ny ++;
 
 			if (nx > 1 || nd > 1) {
 				// The hv intersection (x,y) = (dx, dy*ny ) * (nx-1)/(nx*ny-1)
@@ -355,31 +360,32 @@ void DTW_Path_recode (DTW me) {
 				index = nxy - 1;
 				if (ny == 2) {
 					index = nxy;
-					nxy++;
+					nxy ++;
 				}
-				thy xytimes[index].x = x;
-				thy xytimes[index].y = y;
+				thy xytimes [index]. x = x;
+				thy xytimes [index]. y = y;
 			}
 			nd = 0;
-		} else if (ix == (ixp + 1) && iy == (iyp + 1)) { // diagonal
-			nd++;
+		} else if (ix == ixp + 1 && iy == iyp + 1) { // diagonal
+			nd ++;
 			if (nd == 1) {
-				nxy++;
+				nxy ++;
 			}
 			nx = ny = 1;
 		} else {
 			Melder_throw (U"The path goes back in time.");
 		}
 		// update
-		thy xytimes[nxy].x = xright;
-		thy xytimes[nxy].y = ytop;
-		ixp = ix; iyp = iy;
+		thy xytimes [nxy]. x = xright;
+		thy xytimes [nxy]. y = ytop;
+		ixp = ix;
+		iyp = iy;
 	}
 
-	if (my xmax > thy xytimes[nxy].x || my ymax > thy xytimes[nxy].y) {
-		nxy++;
-		thy xytimes[nxy].x = my xmax;
-		thy xytimes[nxy].y = my ymax;
+	if (my xmax > thy xytimes [nxy]. x || my ymax > thy xytimes [nxy]. y) {
+		nxy ++;
+		thy xytimes [nxy]. x = my xmax;
+		thy xytimes [nxy]. y = my ymax;
 	}
 	Melder_assert (nxy <= 2 * (my ny > my nx ? my ny : my nx) + 2);
 	thy nxy = nxy;
@@ -392,25 +398,25 @@ void DTW_Path_recode (DTW me) {
 void DTW_pathRemoveRedundantNodes (DTW me) {
 	long i = 1, skip = 0;
 
-	for (long j = 2; j <= my pathLength; j++) {
-		if ( (my path[j].y == my path[i].y) || my path[j].x == my path[i].x) {
+	for (long j = 2; j <= my pathLength; j ++) {
+		if ( (my path [j]. y == my path [i]. y) || my path [j]. x == my path [i]. x) {
 			skip++;
 		} else {
 			/* if (j-1)^th was the last of a series: store it */
 			if (skip > 0) {
-				my path[++i] = my path[j - 1];
+				my path [++ i] = my path [j - 1];
 			}
 			/* same check again */
 			skip = 0;
-			if ( (my path[j].y == my path[i].y) || my path[j].x == my path[i].x) {
-				skip++;
+			if ( (my path [j]. y == my path [i]. y) || my path [j]. x == my path [i]. x) {
+				skip ++;
 			} else {
-				my path[++i] = my path[j];
+				my path [++ i] = my path [j];
 			}
 		}
 	}
 	if (skip > 0) {
-		my path[++i] = my path[my pathLength];
+		my path [++ i] = my path [my pathLength];
 	}
 	my pathLength = i;
 }
@@ -423,7 +429,9 @@ autoDTW DTW_create (double tminp, double tmaxp, long ntp, double dtp, double t1p
 		Matrix_init (me.get(), tminc, tmaxc, ntc, dtc, t1c, tminp, tmaxp, ntp, dtp, t1p);
 		my path = NUMvector<structDTW_Path> (1, ntc + ntp - 1);
 		DTW_Path_Query_init (& my pathQuery, ntp, ntc);
-		my wx = 1; my wy = 1; my wd = 2;
+		my wx = 1;
+		my wy = 1;
+		my wd = 2;
 		return me;
 	} catch (MelderError) {
 		Melder_throw (U"DTW not created.");
@@ -431,22 +439,24 @@ autoDTW DTW_create (double tminp, double tmaxp, long ntp, double dtp, double t1p
 }
 
 void DTW_setWeights (DTW me, double wx, double wy, double wd) {
-	my wx = wx; my wy = wy; my wd = wd;
+	my wx = wx;
+	my wy = wy;
+	my wd = wd;
 }
 
 autoDTW DTW_swapAxes (DTW me) {
 	try {
 		autoDTW thee = DTW_create (my xmin, my xmax, my nx, my dx, my x1, my ymin, my ymax, my ny, my dy, my y1);
 
-		for (long x = 1; x <= my nx; x++) {
-			for (long y = 1; y <= my ny; y++) {
-				thy z[x][y] = my z[y][x];
+		for (long x = 1; x <= my nx; x ++) {
+			for (long y = 1; y <= my ny; y ++) {
+				thy z [x] [y] = my z [y] [x];
 			}
 		}
 		thy pathLength = my pathLength;
-		for (long i = 1; i <= my pathLength; i++) {
-			thy path[i].x = my path[i].y;
-			thy path[i].y = my path[i].x;
+		for (long i = 1; i <= my pathLength; i ++) {
+			thy path [i]. x = my path [i]. y;
+			thy path [i]. y = my path [i]. x;
 		}
 		return thee;
 	} catch (MelderError) {
@@ -477,14 +487,14 @@ double DTW_getPathY (DTW me, double tx) {
 
 	// Find index in the path and the row number (iy)
 
-	long i = ix + my path[1].x - 1;
-	while (i <= my pathLength && my path[i].x != ix) {
-		i++;
+	long i = ix + my path [1]. x - 1;
+	while (i <= my pathLength && my path [i]. x != ix) {
+		i ++;
 	}
 	if (i > my pathLength) {
 		return NUMundefined;
 	}
-	long iy = my path[i].y; /* row */
+	long iy = my path [i]. y; /* row */
 
 	/*
 		We like to have a "continuous" interpretation of time for the quantized x and y times.
@@ -505,21 +515,21 @@ double DTW_getPathY (DTW me, double tx) {
 	// Horizontal path? Find left and right positions.
 
 	long ileft = i - 1;
-	while (ileft >= 1 && my path[ileft].y == iy) {
-		ileft--;
+	while (ileft >= 1 && my path [ileft]. y == iy) {
+		ileft --;
 	}
 	ileft++;
-	if (ileft == 1 && ix > 1 && my path[ileft].y > 1) {
-		ileft++;
+	if (ileft == 1 && ix > 1 && my path [ileft]. y > 1) {
+		ileft ++;
 	}
 
 	long iright = i + 1;
-	while (iright <= my pathLength && my path[iright].y == iy) {
-		iright++;
+	while (iright <= my pathLength && my path [iright]. y == iy) {
+		iright ++;
 	}
 	iright--;
-	if (iright == my pathLength && ix < my nx && my path[iright].y < my ny) {
-		iright--;
+	if (iright == my pathLength && ix < my nx && my path [iright]. y < my ny) {
+		iright --;
 	}
 
 	long nxx = iright - ileft + 1;
@@ -530,17 +540,17 @@ double DTW_getPathY (DTW me, double tx) {
 	long itop = i;
 
 	if (nxx == 1) {
-		ibottom--;
-		while (ibottom >= 1 && my path[ibottom].x == ix) {
-			ibottom--;
+		ibottom --;
+		while (ibottom >= 1 && my path [ibottom]. x == ix) {
+			ibottom --;
 		}
-		ibottom++;
+		ibottom ++;
 
-		itop++;
-		while (itop <= 1 && my path[itop].x == ix) {
-			itop--;
+		itop ++;
+		while (itop <= 1 && my path [itop]. x == ix) {
+			itop --;
 		}
-		itop++;
+		itop ++;
 	}
 
 	long nyy = itop - ibottom + 1;
@@ -551,17 +561,17 @@ double DTW_getPathY (DTW me, double tx) {
 	// Corrections at extreme left and right if path[1].x=1 && path[1].y>1
 
 	if (ix == my nx) {
-		boxx = my xmax - (my x1 + (ix - 1) * my dx - my dx / 2);
-		boxy = my ymax - (my y1 + (iy - 1) * my dy - my dy / 2);
+		boxx = my xmax - (my x1 + (ix - 1) * my dx - my dx / 2.0);
+		boxy = my ymax - (my y1 + (iy - 1) * my dy - my dy / 2.0);
 		ty = my ymax - (boxy - (boxx - (my xmax - tx)) * boxy / boxx);
 	} else if (ix == 1) {
-		boxx = my x1 + my dx / 2 - my xmin;
-		boxy = my y1 + (itop - 1) * my dy + my dy / 2 - my ymin;
+		boxx = my x1 + my dx / 2.0 - my xmin;
+		boxy = my y1 + (itop - 1) * my dy + my dy / 2.0 - my ymin;
 		ty = (tx - my xmin) * boxy / boxx + my ymin;
 	} else {
 		// Diagonal interpolation in a box with lower left (0,0) and upper right (nxx*dx, nyy*dy).
-		double ty0 = (tx - (my x1 + (my path[ileft].x - 1) * my dx - my dx / 2)) * boxy / boxx;
-		ty =  my y1 + (my path[ibottom].y - 1) * my dy - my dy / 2 + ty0;
+		double ty0 = (tx - (my x1 + (my path [ileft]. x - 1.0) * my dx - my dx / 2.0)) * boxy / boxx;
+		ty =  my y1 + (my path [ibottom]. y - 1.0) * my dy - my dy / 2.0 + ty0;
 	}
 	return ty;
 }
@@ -569,12 +579,12 @@ double DTW_getPathY (DTW me, double tx) {
 long DTW_getMaximumConsecutiveSteps (DTW me, int direction) {
 	long nglobal = 1, nlocal = 1;
 
-	for (long i = 2; i <= my pathLength; i++) {
+	for (long i = 2; i <= my pathLength; i ++) {
 		int localdirection;
 
-		if (my path[i].y == my path[i - 1].y) {
+		if (my path[i].y == my path [i - 1]. y) {
 			localdirection = DTW_X;
-		} else if (my path[i].x == my path[i - 1].x) {
+		} else if (my path [i]. x == my path [i - 1]. x) {
 			localdirection = DTW_Y;
 		} else {
 			localdirection = DTW_XANDY;
@@ -594,7 +604,7 @@ long DTW_getMaximumConsecutiveSteps (DTW me, int direction) {
 }
 
 static void DTW_paintDistances_raw (DTW me, Graphics g, double xmin, double xmax, double ymin,
-                                    double ymax, double minimum, double maximum, int garnish, int inset) {
+                                    double ymax, double minimum, double maximum, bool garnish, bool inset) {
 	long ixmin, ixmax, iymin, iymax;
 	if (xmax <= xmin) {
 		xmin = my xmin;
@@ -637,8 +647,8 @@ static void DTW_paintDistances_raw (DTW me, Graphics g, double xmin, double xmax
 }
 
 void DTW_paintDistances (DTW me, Graphics g, double xmin, double xmax, double ymin,
-                         double ymax, double minimum, double maximum, int garnish) {
-	DTW_paintDistances_raw (me, g, xmin, xmax, ymin, ymax, minimum, maximum, garnish, 1);
+                         double ymax, double minimum, double maximum, bool garnish) {
+	DTW_paintDistances_raw (me, g, xmin, xmax, ymin, ymax, minimum, maximum, garnish, true);
 }
 
 static double RealTier_getXAtIndex (RealTier me, long point) {
@@ -650,7 +660,7 @@ static double RealTier_getXAtIndex (RealTier me, long point) {
 }
 
 static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, double ymin,
-                              double ymax, int garnish, int inset) {
+                              double ymax, bool garnish, bool inset) {
 	DTW_Path_Query thee = & my pathQuery;
 
 	if (xmin >= xmax) {
@@ -666,11 +676,11 @@ static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, doub
 	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
 	double x1 = RealTier_getXAtIndex (thy yfromx.get(), 1);
 	double y1 = RealTier_getValueAtIndex (thy yfromx.get(), 1);
-	for (long i = 2; i <= thy yfromx -> points.size; i++) {
+	for (long i = 2; i <= thy yfromx -> points.size; i ++) {
 		double x2 = RealTier_getXAtIndex (thy yfromx.get(), i);
 		double y2 = RealTier_getValueAtIndex (thy yfromx.get(), i);
 		double xc1, yc1, xc2, yc2;
-		if (NUMclipLineWithinRectangle (x1, y1,x2, y2, xmin, ymin, xmax, ymax, &xc1, &yc1, &xc2, &yc2)) {
+		if (NUMclipLineWithinRectangle (x1, y1, x2, y2, xmin, ymin, xmax, ymax, & xc1, & yc1, & xc2, & yc2)) {
 			Graphics_line (g, xc1, yc1, xc2, yc2);
 		}
 		x1 = x2; 
@@ -688,12 +698,12 @@ static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, doub
 }
 
 void DTW_drawPath (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, int garnish) {
-	DTW_drawPath_raw (me, g, xmin, xmax, ymin, ymax, garnish, 1);
+	DTW_drawPath_raw (me, g, xmin, xmax, ymin, ymax, garnish, true);
 }
 
-static void DTW_drawWarp_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double t, int garnish, bool inset, bool warpX) {
+static void DTW_drawWarp_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double t, bool garnish, bool inset, bool warpX) {
 	double tx = warpX ? t : DTW_getXTimeFromYTime (me, t);
-	double ty = warpX ? DTW_getYTimeFromXTime (me, t): t;
+	double ty = warpX ? DTW_getYTimeFromXTime (me, t) : t;
 	int lineType = Graphics_inqLineType (g);
 
 	if (xmin >= xmax) {
@@ -730,11 +740,11 @@ static void DTW_drawWarp_raw (DTW me, Graphics g, double xmin, double xmax, doub
 	}
 }
 
-void DTW_drawWarpX (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double tx, int garnish) {
+void DTW_drawWarpX (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double tx, bool garnish) {
 	DTW_drawWarp_raw (me, g, xmin, xmax, ymin, ymax, tx, garnish, true, true);
 }
 
-void DTW_drawWarpY (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double ty, int garnish) {
+void DTW_drawWarpY (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double ty, bool garnish) {
 	DTW_drawWarp_raw (me, g, xmin, xmax, ymin, ymax, ty, garnish, true, false);
 }
 
@@ -753,17 +763,19 @@ static void DTW_and_Sounds_checkDomains (DTW me, Sound *y, Sound *x, double *xmi
 	}
 
 	if (*xmin >= *xmax) {
-		*xmin = my xmin; *xmax = my xmax;
+		*xmin = my xmin;
+		*xmax = my xmax;
 	}
 	if (*ymin >= *ymax) {
-		*ymin = my ymin; *ymax = my ymax;
+		*ymin = my ymin;
+		*ymax = my ymax;
 	}
 }
 
 static void drawBox (Graphics g) {
 	double x1WC, x2WC, y1WC, y2WC;
 	double lineWidth = Graphics_inqLineWidth (g);
-	Graphics_inqWindow (g, &x1WC, &x2WC, &y1WC, &y2WC);
+	Graphics_inqWindow (g, & x1WC, & x2WC, & y1WC, & y2WC);
 	Graphics_setLineWidth (g, 2.0 * lineWidth);
 	Graphics_rectangle (g, x1WC, x2WC, y1WC, y2WC);
 	Graphics_setLineWidth (g, lineWidth);
@@ -776,13 +788,14 @@ static void drawBox (Graphics g) {
 */
 static double _DTW_and_Sounds_getPartY (Graphics g, double dtw_part_x) {
 	double x1NDC, x2NDC, y1NDC, y2NDC;
-	Graphics_inqViewport (g, &x1NDC, &x2NDC, &y1NDC, &y2NDC);
-	return 1 - ( (1 - dtw_part_x) * (x2NDC - x1NDC)) / (y2NDC - y1NDC);
+	Graphics_inqViewport (g, & x1NDC, & x2NDC, & y1NDC, & y2NDC);
+	return 1.0 - ((1.0 - dtw_part_x) * (x2NDC - x1NDC)) / (y2NDC - y1NDC);
 }
 
 void DTW_and_Sounds_draw (DTW me, Sound y, Sound x, Graphics g, double xmin, double xmax,
-                          double ymin, double ymax, int garnish) {
-	DTW_and_Sounds_checkDomains (me, &y, &x, &xmin, &xmax, &ymin, &ymax);
+                          double ymin, double ymax, bool garnish)
+{
+	DTW_and_Sounds_checkDomains (me, & y, & x, & xmin, & xmax, & ymin, & ymax);
 
 	Graphics_setInner (g);
 	Graphics_Viewport ovp = g -> outerViewport; // save for unsetInner
@@ -792,16 +805,16 @@ void DTW_and_Sounds_draw (DTW me, Sound y, Sound x, Graphics g, double xmin, dou
 
 	/* DTW */
 
-	Graphics_Viewport vp = Graphics_insetViewport (g, 1 - dtw_part_x, 1, 1 - dtw_part_y, 1);
-	DTW_paintDistances_raw (me, g, xmin, xmax, ymin, ymax, 0, 0, 0, 0);
-	DTW_drawPath_raw (me, g, xmin, xmax, ymin, ymax, 0, 0);
+	Graphics_Viewport vp = Graphics_insetViewport (g, 1.0 - dtw_part_x, 1.0, 1.0 - dtw_part_y, 1.0);
+	DTW_paintDistances_raw (me, g, xmin, xmax, ymin, ymax, 0.0, 0.0, false, false);
+	DTW_drawPath_raw (me, g, xmin, xmax, ymin, ymax, false, false);
 	drawBox (g);
 	Graphics_resetViewport (g, vp);
 
 	/* Sound y */
 
-	vp = Graphics_insetViewport (g, 0, 1 - dtw_part_x, 1 - dtw_part_y, 1);
-	Sound_draw_btlr (y, g, ymin, ymax, -1, 1, FROM_BOTTOM_TO_TOP, 0);
+	vp = Graphics_insetViewport (g, 0.0, 1.0 - dtw_part_x, 1.0 - dtw_part_y, 1.0);
+	Sound_draw_btlr (y, g, ymin, ymax, -1.0, 1.0, FROM_BOTTOM_TO_TOP, 0);
 	if (garnish) {
 		drawBox (g);
 	}
@@ -810,7 +823,7 @@ void DTW_and_Sounds_draw (DTW me, Sound y, Sound x, Graphics g, double xmin, dou
 	/* Sound x */
 
 	vp = Graphics_insetViewport (g, 1 - dtw_part_x, 1, 0, 1 - dtw_part_y);
-	Sound_draw_btlr (x, g, xmin, xmax, -1, 1, FROM_LEFT_TO_RIGHT, 0);
+	Sound_draw_btlr (x, g, xmin, xmax, -1.0, 1.0, FROM_LEFT_TO_RIGHT, 0);
 	if (garnish) {
 		drawBox (g);
 	}
@@ -836,11 +849,12 @@ void DTW_and_Sounds_draw (DTW me, Sound y, Sound x, Graphics g, double xmin, dou
 }
 
 void DTW_and_Sounds_drawWarpX (DTW me, Sound yy, Sound xx, Graphics g, double xmin, double xmax,
-                               double ymin, double ymax, double tx, int garnish) {
+                               double ymin, double ymax, double tx, bool garnish)
+{
 	Sound y = yy, x = xx;
 	int lineType = Graphics_inqLineType (g);
 
-	DTW_and_Sounds_checkDomains (me, &y, &x, &xmin, &xmax, &ymin, &ymax);
+	DTW_and_Sounds_checkDomains (me, & y, & x, & xmin, & xmax, & ymin, & ymax);
 
 	Graphics_setInner (g);
 	double dtw_part_x = 0.85;
@@ -877,40 +891,40 @@ autoMatrix DTW_to_Matrix_distances (DTW me) {
 }
 
 /* nog aanpassen, dl = sqrt (dx^2 + dy^2) */
-void DTW_drawDistancesAlongPath (DTW me, Graphics g, double xmin, double xmax, double dmin, double dmax, int garnish) {
+void DTW_drawDistancesAlongPath (DTW me, Graphics g, double xmin, double xmax, double dmin, double dmax, bool garnish) {
 	if (xmin >= xmax) {
 		xmin = my xmin; xmax = my xmax;
 	}
 	long ixmax, ixmin;
-	if (! Matrix_getWindowSamplesX (me, xmin, xmax, &ixmin, &ixmax)) {
+	if (! Matrix_getWindowSamplesX (me, xmin, xmax, & ixmin, & ixmax)) {
 		return;
 	}
 
 	long ii = 1;
-	while (ii < my pathLength && my path[ii].x < ixmin) {
-		ii++;
+	while (ii < my pathLength && my path [ii]. x < ixmin) {
+		ii ++;
 	}
 	ixmin = ii;
 
-	while (ii <= my pathLength && my path[ii].x < ixmax) {
-		ii++;
+	while (ii <= my pathLength && my path [ii]. x < ixmax) {
+		ii ++;
 	}
 	ixmax = ii;
 
 	autoNUMvector<double> d (ixmin, ixmax);
 
 	for (long i = ixmin; i <= ixmax; i++) {
-		d[i] = my z[my path[i].y][i];
+		d [i] = my z [my path [i]. y] [i];
 	}
 
 	if (dmin >= dmax) {
-		NUMvector_extrema (d.peek(), ixmin, ixmax, &dmin, &dmax);
+		NUMvector_extrema (d.peek(), ixmin, ixmax, & dmin, & dmax);
 	} else {
-		for (long i = ixmin; i <= ixmax; i++) {
-			if (d[i] > dmax) {
-				d[i] = dmax;
-			} else if (d[i] < dmin) {
-				d[i] = dmin;
+		for (long i = ixmin; i <= ixmax; i ++) {
+			if (d [i] > dmax) {
+				d [i] = dmax;
+			} else if (d [i] < dmin) {
+				d [i] = dmin;
 			}
 		}
 	}
@@ -946,21 +960,21 @@ autoDTW Matrices_to_DTW (Matrix me, Matrix thee, int matchStart, int matchEnd, i
 					is a large number.
 					d = (x^n)^(1/n) may overflow if x>1 & n >>1 even if d would not overflow!
 				*/
-				double dmax = 0, d = 0, dtmp;
-				for (long k = 1; k <= my ny; k++) {
-					dtmp = fabs (my z[k][i] - thy z[k][j]);
+				double dmax = 0.0, d = 0.0;
+				for (long k = 1; k <= my ny; k ++) {
+					double dtmp = fabs (my z [k] [i] - thy z [k] [j]);
 					if (dtmp > dmax) {
 						dmax = dtmp;
 					}
 				}
 				if (dmax > 0) {
-					for (long k = 1; k <= my ny; k++) {
-						dtmp = fabs (my z[k][i] - thy z[k][j]) / dmax;
+					for (long k = 1; k <= my ny; k ++) {
+						double dtmp = fabs (my z [k] [i] - thy z [k] [j]) / dmax;
 						d +=  pow (dtmp, metric);
 					}
 				}
 				d = dmax * pow (d, 1.0 / metric);
-				his z[i][j] = d / my ny; /* == d * dy / ymax */
+				his z [i] [j] = d / my ny; /* == d * dy / ymax */
 			}
 			if ( (i % 10) == 1) {
 				Melder_progress (0.999 * i / my nx, U"Calculate distances: column ", i, U" from ", my nx, U".");
@@ -984,14 +998,14 @@ autoDTW Spectrograms_to_DTW (Spectrogram me, Spectrogram thee, int matchStart, i
 
 		// Take log10 for dB's (4e-10 scaling not necessary)
 
-		for (long i = 1; i <= my ny; i++) {
-			for (long j = 1; j <= my nx; j++) {
-				m1 -> z[i][j] = 10 * log10 (m1 -> z[i][j]);
+		for (long i = 1; i <= my ny; i ++) {
+			for (long j = 1; j <= my nx; j ++) {
+				m1 -> z [i] [j] = 10 * log10 (m1 -> z [i] [j]);
 			}
 		}
-		for (long i = 1; i <= thy ny; i++) {
-			for (long j = 1; j <= thy nx; j++) {
-				m2 -> z[i][j] = 10 * log10 (m2 -> z[i][j]);
+		for (long i = 1; i <= thy ny; i ++) {
+			for (long j = 1; j <= thy nx; j ++) {
+				m2 -> z [i] [j] = 10 * log10 (m2 -> z [i] [j]);
 			}
 		}
 
@@ -1012,7 +1026,7 @@ static int Pitch_findFirstAndLastVoicedFrame (Pitch me, long *first, long *last)
 	}
 	*last = my nx;
 	while (*last >= *first && ! Pitch_isVoiced_i (me, *last)) {
-		(*last)--;
+		(*last) --;
 	}
 	return *first <= my nx && *last >= 1;
 }
@@ -1028,8 +1042,8 @@ autoDTW Pitches_to_DTW_sgc (Pitch me, Pitch thee, double vuv_costs, double time_
 		}
 
 		long myfirst, mylast, thyfirst, thylast;
-		if (! Pitch_findFirstAndLastVoicedFrame (me, &myfirst, &mylast) ||
-			! Pitch_findFirstAndLastVoicedFrame (thee, &thyfirst, &thylast)) {
+		if (! Pitch_findFirstAndLastVoicedFrame (me, & myfirst, & mylast) ||
+			! Pitch_findFirstAndLastVoicedFrame (thee, & thyfirst, & thylast)) {
 			Melder_throw (U"No voiced frames.");
 		}
 		/*
@@ -1042,7 +1056,7 @@ autoDTW Pitches_to_DTW_sgc (Pitch me, Pitch thee, double vuv_costs, double time_
 		autoNUMvector<double> pitchx (1, thy nx);
 		int unit = kPitch_unit_SEMITONES_100;
 		for (long j = 1; j <= thy nx; j++) {
-			pitchx[j] = Sampled_getValueAtSample (thee, j, Pitch_LEVEL_FREQUENCY, unit);
+			pitchx [j] = Sampled_getValueAtSample (thee, j, Pitch_LEVEL_FREQUENCY, unit);
 		}
 
 		for (long i = 1; i <= my nx; i++) {
@@ -1050,18 +1064,18 @@ autoDTW Pitches_to_DTW_sgc (Pitch me, Pitch thee, double vuv_costs, double time_
 			double t1 = my x1 + (i - 1) * my dx;
 			for (long j = 1; j <= thy nx; j++) {
 				double t2 = thy x1 + (j - 1) * thy dx;
-				double dist_f = 0; // based on pitch difference
+				double dist_f = 0.0; // based on pitch difference
 				double dist_t = fabs (t1 - t2);
 				if (pitchy == NUMundefined) {
-					if (pitchx[j] != NUMundefined) {
+					if (pitchx [j] != NUMundefined) {
 						dist_f = vuv_costs;
 					}
-				} else if (pitchx[j] == NUMundefined) {
+				} else if (pitchx [j] == NUMundefined) {
 					dist_f = vuv_costs;
 				} else {
 					dist_f = fabs (pitchy - pitchx[j]);
 				}
-				his z[i][j] = sqrt (dist_f * dist_f + time_weight * dist_t * dist_t);
+				his z [i] [j] = sqrt (dist_f * dist_f + time_weight * dist_t * dist_t);
 			}
 		}
 		DTW_findPath (him.get(), matchStart, matchEnd, slope);
@@ -1083,11 +1097,11 @@ autoDTW Pitches_to_DTW (Pitch me, Pitch thee, double vuv_costs, double time_weig
 		autoDTW him = DTW_create (my xmin, my xmax, my nx, my dx, my x1, thy xmin, thy xmax, thy nx, thy dx, thy x1);
 		autoNUMvector<double> pitchx (1, thy nx);
 		int unit = kPitch_unit_SEMITONES_100;
-		for (long j = 1; j <= thy nx; j++) {
+		for (long j = 1; j <= thy nx; j ++) {
 			pitchx[j] = Sampled_getValueAtSample (thee, j, Pitch_LEVEL_FREQUENCY, unit);
 		}
 
-		for (long i = 1; i <= my nx; i++) {
+		for (long i = 1; i <= my nx; i ++) {
 			double pitchy = Sampled_getValueAtSample (me, i, Pitch_LEVEL_FREQUENCY, unit);
 			double t1 = my x1 + (i - 1) * my dx;
 			for (long j = 1; j <= thy nx; j++) {
@@ -1095,15 +1109,15 @@ autoDTW Pitches_to_DTW (Pitch me, Pitch thee, double vuv_costs, double time_weig
 				double dist_f = 0; // based on pitch difference
 				double dist_t = fabs (t1 - t2);
 				if (pitchy == NUMundefined) {
-					if (pitchx[j] != NUMundefined) {
+					if (pitchx [j] != NUMundefined) {
 						dist_f = vuv_costs;
 					}
-				} else if (pitchx[j] == NUMundefined) {
+				} else if (pitchx [j] == NUMundefined) {
 					dist_f = vuv_costs;
 				} else {
-					dist_f = fabs (pitchy - pitchx[j]);
+					dist_f = fabs (pitchy - pitchx [j]);
 				}
-				his z[i][j] = sqrt (dist_f * dist_f + time_weight * dist_t * dist_t);
+				his z [i] [j] = sqrt (dist_f * dist_f + time_weight * dist_t * dist_t);
 			}
 		}
 
@@ -1143,31 +1157,38 @@ void DTW_findPath (DTW me, int matchStart, int matchEnd, int slope) {
     DTW_findPath_special (me, matchStart, matchEnd, slope, nullptr);
 }
 
-autoMatrix DTW_to_Matrix_cummulativeDistances (DTW me, double sakoeChibaBand, int slope) {
+autoMatrix DTW_to_Matrix_cumulativeDistances (DTW me, double sakoeChibaBand, int slope) {
     try {
-        autoMatrix cummulativeDistances;
-        DTW_findPath_bandAndSlope (me, sakoeChibaBand, slope, &cummulativeDistances);
-        return cummulativeDistances;
+        autoMatrix cumulativeDistances;
+        DTW_findPath_bandAndSlope (me, sakoeChibaBand, slope, & cumulativeDistances);
+        return cumulativeDistances;
     } catch (MelderError) {
-        Melder_throw (me, U": cummulative costs matrix not created.");
+        Melder_throw (me, U": cumulative costs matrix not created.");
     }
 }
 
+
+static void DTW_relaxConstraints (DTW me, double band, int slope, double *relaxedBand, int *relaxedSlope) {
+	double dtw_slope = (my ymax - my ymin - band) / (my xmax - my xmin - band);
+	*relaxedBand = 0.0;
+	*relaxedSlope = 1;
+}
+
 static void DTW_checkSlopeConstraints (DTW me, double band, int slope) {
     try {
-        double slopes[5] = { DTW_BIG, DTW_BIG, 3, 2, 1.5 } ;
+        double slopes[5] = { DTW_BIG, DTW_BIG, 3.0, 2.0, 1.5 } ;
         double dtw_slope = (my ymax - my ymin - band) / (my xmax - my xmin - band);
         if (slope < 1 || slope > 4) {
             Melder_throw (U"Invalid slope constraint.");
         }
-        if (dtw_slope <= 0 && slope != 1) {
+        if (dtw_slope <= 0.0 && slope != 1) {
             Melder_throw (U"Band too wide.");
         }
-        if (dtw_slope < 1) {
-            dtw_slope = 1 / dtw_slope;
+        if (dtw_slope < 1.0) {
+            dtw_slope = 1.0 / dtw_slope;
         }
-            if (dtw_slope > slopes[slope]) {
-            Melder_warning (U"There is a conflict between the chosen slope constraint and the relative duration. "
+        if (dtw_slope > slopes [slope]) {
+            Melder_throw (U"There is a conflict between the chosen slope constraint and the relative duration. "
 				"The duration ratio of the longest and the shortest object is ", dtw_slope,
 				U". This implies that the largest slope in the constraint must have a value greater or equal to this ratio.");
         }
@@ -1178,23 +1199,23 @@ static void DTW_checkSlopeConstraints (DTW me, double band, int slope) {
 
 static void DTW_and_Polygon_setUnreachableParts (DTW me, Polygon thee, long **psi) {
     try {
-        double eps = my dx / 100; // safely enough
+        double eps = my dx / 100.0;   // safe enough
         double dtw_slope = (my ymax - my ymin) / (my xmax - my xmin);
 
         double xmin, xmax, ymin, ymax;
-        Polygon_getExtrema (thee, &xmin, &xmax, &ymin, &ymax);
+        Polygon_getExtrema (thee, & xmin, & xmax, & ymin, & ymax);
         // if the Polygon and the DTW don't overlap everything is unreachable!
         if (xmax <= my xmin || xmin >= my xmax || ymax <= my ymin || ymin >= my ymax) {
             Melder_throw (U"DTW and Polygon don't overlap.");
         }
         // find border "above" polygon
-        for (long ix = 1; ix <= my nx; ix++) {
+        for (long ix = 1; ix <= my nx; ix ++) {
             double x = my x1 + (ix - 1) * my dx;
             long iystart = (long) floor (dtw_slope * ix * (my dx / my dy)) + 1;
-            for (long iy = iystart + 1; iy <= my ny; iy++) {
+            for (long iy = iystart + 1; iy <= my ny; iy ++) {
                 double y = my y1 + (iy - 1) * my dy;
                 if (Polygon_getLocationOfPoint (thee, x, y, eps) == Polygon_OUTSIDE) {
-                    for (long k = iy; k <= my ny; k++) {
+                    for (long k = iy; k <= my ny; k ++) {
                         psi[k][ix] =  DTW_UNREACHABLE;
                     }
                     break;
@@ -1202,14 +1223,14 @@ static void DTW_and_Polygon_setUnreachableParts (DTW me, Polygon thee, long **ps
             }
         }
         // find border "below" polygon
-        for (long ix = 2; ix <= my nx; ix++) {
+        for (long ix = 2; ix <= my nx; ix ++) {
             double x = my x1 + (ix - 1) * my dx;
-            long iystart = (long) floor (dtw_slope * ix * (my dx / my dy)); // start 1 lower
+            long iystart = (long) floor (dtw_slope * ix * (my dx / my dy));   // start 1 lower
             if (iystart > my ny) iystart = my ny;
-            for (long iy = iystart - 1; iy >= 1; iy--) {
+            for (long iy = iystart - 1; iy >= 1; iy --) {
                 double y = my y1 + (iy - 1) * my dy;
                 if (Polygon_getLocationOfPoint (thee, x, y, eps) == Polygon_OUTSIDE) {
-                    for (long k = iy; k >= 1; k--) {
+                    for (long k = iy; k >= 1; k --) {
                         psi[k][ix] = DTW_UNREACHABLE;
                     }
                     break;
@@ -1223,12 +1244,12 @@ static void DTW_and_Polygon_setUnreachableParts (DTW me, Polygon thee, long **ps
 }
 
 #define DTW_ISREACHABLE(y,x) ((psi[y][x] != DTW_UNREACHABLE) && (psi[y][x] != DTW_FORBIDDEN))
-static void DTW_findPath_special (DTW me, int matchStart, int matchEnd, int slope, autoMatrix *cummulativeDists) {
+static void DTW_findPath_special (DTW me, int matchStart, int matchEnd, int slope, autoMatrix *cumulativeDists) {
     (void) matchStart;
     (void) matchEnd;
 	try {
        autoPolygon thee = DTW_to_Polygon (me, 0.0, slope);
-       DTW_and_Polygon_findPathInside (me, thee.get(), slope, cummulativeDists);
+       DTW_and_Polygon_findPathInside (me, thee.get(), slope, cumulativeDists);
 	} catch (MelderError) {
 		Melder_throw (me, U": cannot find path.");
 	}
@@ -1237,76 +1258,100 @@ static void DTW_findPath_special (DTW me, int matchStart, int matchEnd, int slop
 // Intersection of two straight lines y = a[i]*x+b[i], where a[2] = 1 / a[1]. Point (x1,y1) is on first line,
 // point (x2,y2) is on second line.
 static void getIntersectionPoint (double x1, double y1, double x2, double y2, double a, double *x3, double *y3) {
-    *x3 = (y2 - y1 + a * x1 - x2 / a) / (a - 1 / a);
+    *x3 = (y2 - y1 + a * x1 - x2 / a) / (a - 1.0 / a);
     *y3 = a * *x3 + y1 - a * x1;
 }
 
 autoPolygon DTW_to_Polygon (DTW me, double band, int slope) {
     try {
-        DTW_checkSlopeConstraints (me, band, slope);
-        double slopes[5] = { DTW_BIG, DTW_BIG, 3, 2, 1.5 } ;
+		try {
+			DTW_checkSlopeConstraints (me, band, slope);
+		} catch (MelderError) {
+			DTW_relaxConstraints (me, band, slope, & band, & slope);
+			Melder_flushError ();
+		}
+			
+        double slopes[5] = { DTW_BIG, DTW_BIG, 3.0, 2.0, 1.5 } ;
         if (band <= 0) {
             if (slope == 1) {
                 autoPolygon thee = Polygon_create (4);
-                thy x[1] = my xmin; thy y[1] = my ymin;
-                thy x[2] = my xmin; thy y[2] = my ymax;
-                thy x[3] = my xmax; thy y[3] = my ymax;
-                thy x[4] = my xmax; thy y[4] = my ymin;
+                thy x [1] = my xmin;
+				thy y [1] = my ymin;
+                thy x [2] = my xmin;
+				thy y [2] = my ymax;
+                thy x [3] = my xmax;
+				thy y [3] = my ymax;
+                thy x [4] = my xmax;
+				thy y [4] = my ymin;
                 return thee;
             } else {
                 autoPolygon thee = Polygon_create (4);
-                thy x[1] = my xmin; thy y[1] = my ymin;
-                thy x[3] = my xmax; thy y[3] = my ymax;
+                thy x [1] = my xmin;
+				thy y [1] = my ymin;
+                thy x [3] = my xmax;
+				thy y [3] = my ymax;
                 double x, y;
-                getIntersectionPoint (my xmin, my ymin, my xmax, my ymax, slopes[slope], &x, &y);
+                getIntersectionPoint (my xmin, my ymin, my xmax, my ymax, slopes [slope], & x, & y);
                 if (x < my xmin) x = my xmin;
                 if (x > my xmax) x = my xmax;
                 if (y < my ymin) y = my ymin;
                 if (y > my ymax) y = my ymax;
-                thy x[2] = x;
-                thy y[2] = y;
-                getIntersectionPoint (my xmin, my ymin, my xmax, my ymax, 1 / slopes[slope], &x, &y);
+                thy x [2] = x;
+                thy y [2] = y;
+                getIntersectionPoint (my xmin, my ymin, my xmax, my ymax, 1.0 / slopes [slope], & x, & y);
                 if (x < my xmin) x = my xmin;
                 if (x > my xmax) x = my xmax;
                 if (y < my ymin) y = my ymin;
                 if (y > my ymax) y = my ymax;
-                thy x[4] = x;
-                thy y[4] = y;
+                thy x [4] = x;
+                thy y [4] = y;
                 return thee;
             }
         } else {
             if (slope == 1) {
                 autoPolygon thee = Polygon_create (6);
-                thy x[1] = my xmin; thy y[1] = my ymin;
-                thy x[2] = my xmin; thy y[2] = my ymin + band;
-                thy x[3] = my xmax - band; thy y[3] = my ymax;
-                thy x[4] = my xmax; thy y[4] = my ymax;
-                thy x[5] = my xmax; thy y[5] = my ymax - band;
-                thy x[6] = my xmin + band; thy y[6] = my ymin;
+                thy x [1] = my xmin;
+				thy y [1] = my ymin;
+                thy x [2] = my xmin;
+				thy y [2] = my ymin + band;
+                thy x [3] = my xmax - band;
+				thy y [3] = my ymax;
+                thy x [4] = my xmax;
+				thy y [4] = my ymax;
+                thy x [5] = my xmax;
+				thy y [5] = my ymax - band;
+                thy x [6] = my xmin + band;
+				thy y [6] = my ymin;
                 return thee;
             } else {
                 autoPolygon thee = Polygon_create (8);
                 double x, y;
-                thy x[1] = my xmin; thy y[1] = my ymin;
-                thy x[2] = my xmin; thy y[2] = my ymin + band;
-                getIntersectionPoint (my xmin, my ymin + band, my xmax - band, my ymax, slopes[slope], &x, &y);
+                thy x [1] = my xmin;
+				thy y [1] = my ymin;
+                thy x [2] = my xmin;
+				thy y [2] = my ymin + band;
+                getIntersectionPoint (my xmin, my ymin + band, my xmax - band, my ymax, slopes [slope], & x, & y);
                 if (x < my xmin) x = my xmin;
                 if (x > my xmax) x = my xmax;
                 if (y < my ymin) y = my ymin;
                 if (y > my ymax) y = my ymax;
-                thy x[3] = x;
-                thy y[3] = y;
-                thy x[4] = my xmax - band; thy y[4] = my ymax;
-                thy x[5] = my xmax; thy y[5]= my ymax;
-                thy x[6] = my xmax; thy y[6] = my ymax - band;
-                getIntersectionPoint (my xmin + band, my ymin, my xmax, my ymax - band, 1 / slopes[slope], &x, &y);
+                thy x [3] = x;
+                thy y [3] = y;
+                thy x [4] = my xmax - band;
+				thy y [4] = my ymax;
+                thy x [5] = my xmax;
+				thy y [5]= my ymax;
+                thy x [6] = my xmax;
+				thy y [6] = my ymax - band;
+                getIntersectionPoint (my xmin + band, my ymin, my xmax, my ymax - band, 1.0 / slopes [slope], & x, & y);
                 if (x < my xmin) x = my xmin;
                 if (x > my xmax) x = my xmax;
                 if (y < my ymin) y = my ymin;
                 if (y > my ymax) y = my ymax;
-                thy x[7] = x;
-                thy y[7] = y;
-                thy x[8] = my xmin + band; thy y[8] = my ymin;
+                thy x [7] = x;
+                thy y [7] = y;
+                thy x [8] = my xmin + band;
+				thy y [8] = my ymin;
                 return thee;
             }
         }
@@ -1315,28 +1360,28 @@ autoPolygon DTW_to_Polygon (DTW me, double band, int slope) {
     }
 }
 
-autoMatrix DTW_and_Polygon_to_Matrix_cummulativeDistances (DTW me, Polygon thee, int localSlope) {
+autoMatrix DTW_and_Polygon_to_Matrix_cumulativeDistances (DTW me, Polygon thee, int localSlope) {
     try {
-        autoMatrix cummulativeDistances;
-        DTW_and_Polygon_findPathInside (me, thee, localSlope, &cummulativeDistances);
-        return cummulativeDistances;
+        autoMatrix cumulativeDistances;
+        DTW_and_Polygon_findPathInside (me, thee, localSlope, & cumulativeDistances);
+        return cumulativeDistances;
     } catch (MelderError) {
-        Melder_throw (me, U": cummulative costs matrix not created from DTW and Polygon.");
+        Melder_throw (me, U": cumulative costs matrix not created from DTW and Polygon.");
     }
 }
 
-void DTW_findPath_bandAndSlope (DTW me, double sakoeChibaBand, int localSlope, autoMatrix *cummulativeDists) {
+void DTW_findPath_bandAndSlope (DTW me, double sakoeChibaBand, int localSlope, autoMatrix *cumulativeDists) {
     try {
         autoPolygon thee = DTW_to_Polygon (me, sakoeChibaBand, localSlope);
-        DTW_and_Polygon_findPathInside (me, thee.get(), localSlope, cummulativeDists);
+        DTW_and_Polygon_findPathInside (me, thee.get(), localSlope, cumulativeDists);
     } catch (MelderError) {
         Melder_throw (me, U" cannot determine the path.");
     }
 }
 
-void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoMatrix *cummulativeDists) {
+void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoMatrix *cumulativeDists) {
     try {
-        double slopes[5] = { DTW_BIG, DTW_BIG, 3, 2, 1.5 };
+        double slopes [5] = { DTW_BIG, DTW_BIG, 3.0, 2.0, 1.5 };
         // if localSlope == 1 start of path is within 10% of minimum duration. Starts farther away
         long delta_xy = (my nx < my ny ? my nx : my ny) / 10; // if localSlope == 1 start within 10% of
 
@@ -1346,20 +1391,20 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
 
         autoNUMmatrix<double> delta (-2, my ny, -2, my nx);
         autoNUMmatrix<long> psi (-2, my ny, -2, my nx);
-        for (long i = 1; i <= my ny; i++) {
-            for (long j = 1; j <= my nx; j++) {
-                delta[i][j] = my z[i][j];
+        for (long i = 1; i <= my ny; i ++) {
+            for (long j = 1; j <= my nx; j ++) {
+                delta [i] [j] = my z [i] [j];
             }
         }
         // start by making the outside unreachable
-        for (long k = -2; k <= 1; k++) {
-           for (long j = -2; j <= my nx; j++) {
-               // delta[k][j] = DTW_BIG;
-                psi[k][j] = DTW_UNREACHABLE;
+        for (long k = -2; k <= 1; k ++) {
+           for (long j = -2; j <= my nx; j ++) {
+               // delta [k] [j] = DTW_BIG;
+                psi [k] [j] = DTW_UNREACHABLE;
             }
-            for (long i = 1; i <= my ny; i++) {
-             //   delta[i][k] = DTW_BIG;
-                psi[i][k] = DTW_UNREACHABLE;
+            for (long i = 1; i <= my ny; i ++) {
+             //   delta [i] [k] = DTW_BIG;
+                psi [i] [k] = DTW_UNREACHABLE;
             }
         }
 
@@ -1368,12 +1413,12 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
         if (localSlope != 1) {
 			rowto = (long) floor (slopes[localSlope]) + 1;
 		}
-        for (long iy = 2; iy <= rowto; iy++) {
+        for (long iy = 2; iy <= rowto; iy ++) {
             if (localSlope != 1) {
-                delta[iy][1] = delta[iy - 1][1] + my z[iy][1];
-                psi[iy][1] = DTW_Y;
+                delta [iy] [1] = delta [iy - 1] [1] + my z [iy] [1];
+                psi [iy] [1] = DTW_Y;
             } else {
-                psi[iy][1] = DTW_START; // will be adapted by DTW_and_Polygon_setUnreachableParts
+                psi [iy] [1] = DTW_START; // will be adapted by DTW_and_Polygon_setUnreachableParts
             }
         }
         // Make begin part of first row reachable
@@ -1381,12 +1426,12 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
         if (localSlope != 1) {
 			colto = (long) floor (slopes[localSlope]) + 1;
 		}
-        for (long ix = 2; ix <= colto; ix++) {
+        for (long ix = 2; ix <= colto; ix ++) {
             if (localSlope != 1) {
-                delta[1][ix] = delta[1][ix -1] + my z[1][ix];
-                psi[1][ix] = DTW_X;
+                delta [1] [ix] = delta [1] [ix -1] + my z [1] [ix];
+                psi [1] [ix] = DTW_X;
             } else {
-                psi[1][ix] = DTW_START; // will be adapted by DTW_and_Polygon_setUnreachableParts
+                psi [1] [ix] = DTW_START; // will be adapted by DTW_and_Polygon_setUnreachableParts
            }
         }
 
@@ -1396,19 +1441,19 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
         // Forward pass.
         long numberOfIsolatedPoints = 0;
         autoMelderProgress progress (U"Find path");
-        for (long j = 2; j <= my nx; j++) {
-            for (long i = 2; i <= my ny; i++) {
+        for (long j = 2; j <= my nx; j ++) {
+            for (long i = 2; i <= my ny; i ++) {
                 if (! DTW_ISREACHABLE (i, j)) continue;
                 double g, gmin = DTW_BIG;
                 long direction = 0;
                 if (DTW_ISREACHABLE (i - 1, j - 1)) {
-                    gmin = delta[i - 1][j - 1] + 2 * my z[i][j];
+                    gmin = delta [i - 1] [j - 1] + 2.0 * my z [i] [j];
                     direction = DTW_XANDY;
                 } else if (DTW_ISREACHABLE (i, j - 1)) {
-                    gmin = delta[i][j - 1] + my z[i][j];
+                    gmin = delta [i] [j - 1] + my z [i] [j];
                     direction = DTW_X;
                 } else if (DTW_ISREACHABLE (i - 1, j)) {
-                    gmin = delta[i - 1][j] + my z[i][j];
+                    gmin = delta [i - 1] [j] + my z [i] [j];
                     direction = DTW_Y;
                 } else {
                     numberOfIsolatedPoints++;
@@ -1417,11 +1462,11 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
 
                 switch (localSlope) {
                 case 1:  { // no restriction
-                    if (DTW_ISREACHABLE (i, j - 1) && ((g = delta[i][j - 1] + my z[i][j]) < gmin)) {
+                    if (DTW_ISREACHABLE (i, j - 1) && ((g = delta [i] [j - 1] + my z [i] [j]) < gmin)) {
                         gmin = g;
                         direction = DTW_X;
                     }
-                    if (DTW_ISREACHABLE (i - 1, j) && ((g = delta[i - 1][j] + my z[i][j]) < gmin)) {
+                    if (DTW_ISREACHABLE (i - 1, j) && ((g = delta [i - 1] [j] + my z [i] [j]) < gmin)) {
                         gmin = g;
                         direction = DTW_Y;
                     }
@@ -1431,23 +1476,23 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
                 // P = 1/2
 
                 case 2: { // P = 1/2
-                    if (DTW_ISREACHABLE (i - 1, j - 3) && psi[i][j - 1] == DTW_X && psi[i][j - 2] == DTW_XANDY &&
-                        (g = delta[i-1][j-3] + 2 * my z[i][j-2] + my z[i][j-1] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 1, j - 3) && psi [i] [j - 1] == DTW_X && psi [i] [j - 2] == DTW_XANDY &&
+                        (g = delta [i-1] [j-3] + 2.0 * my z [i] [j-2] + my z [i] [j-1] + my z [i] [j]) < gmin) {
                         gmin = g;
                         direction = DTW_X;
                     }
-                    if (DTW_ISREACHABLE (i - 1, j - 2) && psi[i][j - 1] == DTW_XANDY &&
-                        (g = delta[i - 1][j - 2] + 2 * my z[i][j - 1] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 1, j - 2) && psi [i] [j - 1] == DTW_XANDY &&
+                        (g = delta [i - 1] [j - 2] + 2.0 * my z [i] [j - 1] + my z [i] [j]) < gmin) {
                         gmin = g;
                         direction = DTW_X;
                     }
                     if (DTW_ISREACHABLE (i - 2, j - 1) && psi[i - 1][j] == DTW_XANDY &&
-                        (g = delta[i - 2][j - 1] + 2 * my z[i - 1][j] + my z[i][j]) < gmin) {
+                        (g = delta [i - 2] [j - 1] + 2.0 * my z [i - 1] [j] + my z [i] [j]) < gmin) {
                         gmin = g;
                         direction = DTW_Y;
                     }
-                    if (DTW_ISREACHABLE (i - 3, j - 1) && psi[i - 1][j] == DTW_Y && psi[i - 2][j] == DTW_XANDY &&
-                        (g = delta[i-3][j-1] + 2 * my z[i-2][j] + my z[i-1][j] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 3, j - 1) && psi [i - 1] [j] == DTW_Y && psi [i - 2] [j] == DTW_XANDY &&
+                        (g = delta [i-3] [j-1] + 2.0 * my z [i-2] [j] + my z [i-1] [j] + my z [i] [j]) < gmin) {
                         gmin = g;
                         direction = DTW_Y;
                     }
@@ -1457,13 +1502,13 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
                 // P = 1
 
                 case 3: {
-                    if (DTW_ISREACHABLE (i - 1, j - 2) && psi[i][j - 1] == DTW_XANDY &&
-                        (g = delta[i - 1][j - 2] + 2 * my z[i][j - 1] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 1, j - 2) && psi [i] [j - 1] == DTW_XANDY &&
+                        (g = delta [i - 1] [j - 2] + 2.0 * my z [i] [j - 1] + my z [i] [j]) < gmin) {
                         gmin = g;
                         direction = DTW_X;
                     }
-                    if (DTW_ISREACHABLE (i - 2, j - 1) && psi[i - 1][j] == DTW_XANDY &&
-                        (g = delta[i - 2][j - 1] + 2 * my z[i - 1][j] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 2, j - 1) && psi [i - 1] [j] == DTW_XANDY &&
+                        (g = delta [i - 2] [j - 1] + 2.0 * my z [i - 1] [j] + my z [i] [j]) < gmin) {
                         gmin = g;
                         direction = DTW_Y;
                     }
@@ -1473,13 +1518,13 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
                 // P = 2
 
                 case 4: {
-                    if (DTW_ISREACHABLE (i - 2, j - 3) && psi[i][j - 1] == DTW_XANDY && psi[i - 1][j - 2] == DTW_XANDY &&
-                        (g = delta[i-2][j-3] + 2 * my z[i-1][j-2] + 2 * my z[i][j-1] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 2, j - 3) && psi [i] [j - 1] == DTW_XANDY && psi [i - 1] [j - 2] == DTW_XANDY &&
+                        (g = delta [i-2] [j-3] + 2.0 * my z [i-1] [j-2] + 2.0 * my z [i] [j-1] + my z [i] [j]) < gmin) {
                             gmin = g;
                             direction = DTW_X;
                     }
-                    if (DTW_ISREACHABLE (i - 3, j - 2) && psi[i - 1][j] == DTW_XANDY && psi[i - 2][j - 1] == DTW_XANDY &&
-                        (g = delta[i-3][j-2] + 2 * my z[i-2][j-1] + 2 * my z[i-1][j] + my z[i][j]) < gmin) {
+                    if (DTW_ISREACHABLE (i - 3, j - 2) && psi [i - 1] [j] == DTW_XANDY && psi [i - 2] [j - 1] == DTW_XANDY &&
+                        (g = delta [i-3] [j-2] + 2.0 * my z [i-2] [j-1] + 2.0 * my z [i-1] [j] + my z [i] [j]) < gmin) {
                             gmin = g;
                             direction = DTW_Y;
                     }
@@ -1489,8 +1534,8 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
                 break;
                 }
                 Melder_assert (direction != 0);
-                psi[i][j] = direction;
-                delta[i][j] = gmin;
+                psi [i] [j] = direction;
+                delta [i] [j] = gmin;
             }
             if ((j % 10) == 2) {
                 Melder_progress (0.999 * j / my nx, U"Calculate time warp: frame ", j, U" from ", my nx, U".");
@@ -1500,56 +1545,56 @@ void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoM
         // Find minimum at end of path and trace back.
 
         long iy = my ny;
-        double minimum = delta[iy][my nx];
-        for (long i = my ny - 1; i > 0; i--) {
+        double minimum = delta [iy] [my nx];
+        for (long i = my ny - 1; i > 0; i --) {
             if (! DTW_ISREACHABLE (i, my nx)) {
-                break; // we're in unreachable places
-            } else if (delta[i][my nx] < minimum) {
-                minimum = delta[iy = i][my nx];
+                break;   // we're in unreachable places
+            } else if (delta [i] [my nx] < minimum) {
+                minimum = delta [iy = i] [my nx];
             }
         }
         
-        long pathIndex = my nx + my ny - 1; /* Maximum path length */
+        long pathIndex = my nx + my ny - 1;   // maximum path length
         my weightedDistance = minimum / (my nx + my ny);
-        my path[pathIndex].y = iy;
-        long ix = my path[pathIndex].x = my nx;
+        my path [pathIndex]. y = iy;
+        long ix = my path [pathIndex]. x = my nx;
 
         // Fill path backwards.
 
         while (ix > 1) {
-            if (psi[iy][ix] == DTW_XANDY) {
-                ix--;
-                iy--;
-            } else if (psi[iy][ix] == DTW_X) {
-                ix--;
-            } else if (psi[iy][ix] == DTW_Y) {
-                iy--;
-            } else if (psi[iy][ix] == DTW_START) {
+            if (psi [iy] [ix] == DTW_XANDY) {
+                ix --;
+                iy --;
+            } else if (psi [iy] [ix] == DTW_X) {
+                ix --;
+            } else if (psi [iy] [ix] == DTW_Y) {
+                iy --;
+            } else if (psi [iy] [ix] == DTW_START) {
                 break;
             }
             if (pathIndex < 2 || iy < 1) break;
             //Melder_assert (pathIndex > 1 && iy > 0);
-            my path[--pathIndex].x = ix;
-            my path[pathIndex].y = iy;
+            my path [-- pathIndex]. x = ix;
+            my path [pathIndex]. y = iy;
         }
 
         my pathLength = my nx + my ny - 1 - pathIndex + 1;
         if (pathIndex > 1) {
-            for (long j = 1; j <= my pathLength; j++) {
-                my path[j] = my path[pathIndex++];
+            for (long j = 1; j <= my pathLength; j ++) {
+                my path [j] = my path [pathIndex ++];
             }
         }
 
         DTW_Path_recode (me);
-        if (cummulativeDists) {
+        if (cumulativeDists) {
             autoMatrix him = Matrix_create (my xmin, my xmax, my nx, my dx, my x1,
                 my ymin, my ymax, my ny, my dy, my y1);
-            for (long i = 1; i <= my ny; i++) {
-                for (long j = 1; j <= my nx; j++) {
-                    his z[i][j] = delta[i][j];
+            for (long i = 1; i <= my ny; i ++) {
+                for (long j = 1; j <= my nx; j ++) {
+                    his z [i] [j] = delta [i] [j];
                 }
             }
-            *cummulativeDists = him.move();
+            *cumulativeDists = him.move();
         }
     } catch (MelderError) {
         Melder_throw (me, U": cannot find path.");
diff --git a/dwtools/DTW.h b/dwtools/DTW.h
index e7eeb3d..33060c5 100644
--- a/dwtools/DTW.h
+++ b/dwtools/DTW.h
@@ -51,7 +51,7 @@ void DTW_setWeights (DTW me, double wx, double wy, double wd);
 
 autoDTW DTW_swapAxes (DTW me);
 
-void DTW_findPath_bandAndSlope (DTW me, double sakoeChibaBand, int localSlope, autoMatrix *cummulativeDists);
+void DTW_findPath_bandAndSlope (DTW me, double sakoeChibaBand, int localSlope, autoMatrix *cumulativeDists);
 
 void DTW_findPath (DTW me, int matchStart, int matchEnd, int slope); // deprecated
 /* Obsolete
@@ -83,35 +83,35 @@ double DTW_getPathY (DTW me, double tx);
 long DTW_getMaximumConsecutiveSteps (DTW me, int direction);
 
 void DTW_paintDistances (DTW me, Graphics g, double xmin, double xmax, double ymin,
-	double ymax, double minimum, double maximum, int garnish);
+	double ymax, double minimum, double maximum, bool garnish);
 
 void DTW_drawPath (DTW me, Graphics g, double xmin, double xmax, double ymin,
 	double ymax, int garnish);
 
-void DTW_drawWarpX (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double tx, int garnish);
-void DTW_drawWarpY (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double ty, int garnish);
+void DTW_drawWarpX (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double tx, bool garnish);
+void DTW_drawWarpY (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double ty, bool garnish);
 
 void DTW_pathRemoveRedundantNodes (DTW me);
 
 void DTW_pathQueryRecode (DTW me);
 
-void DTW_drawDistancesAlongPath (DTW me, Graphics g, double xmin, double xmax, double dmin, double dmax, int garnish);
+void DTW_drawDistancesAlongPath (DTW me, Graphics g, double xmin, double xmax, double dmin, double dmax, bool garnish);
 
 void DTW_and_Sounds_draw (DTW me, Sound yy, Sound xx, Graphics g, double xmin, double xmax,
-	double ymin, double ymax, int garnish);
+	double ymin, double ymax, bool garnish);
 
 void DTW_and_Sounds_drawWarpX (DTW me, Sound yy, Sound xx, Graphics g, double xmin, double xmax,
-	double ymin, double ymax, double tx, int garnish);
+	double ymin, double ymax, double tx, bool garnish);
 
 autoPolygon DTW_to_Polygon (DTW me, double band, int slope);
 
-void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoMatrix *cummulativeDists);
+void DTW_and_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoMatrix *cumulativeDists);
 
-autoMatrix DTW_to_Matrix_distances(DTW me);
+autoMatrix DTW_to_Matrix_distances (DTW me);
 
-autoMatrix DTW_to_Matrix_cummulativeDistances (DTW me, double sakoeChibaBand, int slope);
+autoMatrix DTW_to_Matrix_cumulativeDistances (DTW me, double sakoeChibaBand, int slope);
 
-autoMatrix DTW_and_Polygon_to_Matrix_cummulativeDistances (DTW me, Polygon thee, int localSlope);
+autoMatrix DTW_and_Polygon_to_Matrix_cumulativeDistances (DTW me, Polygon thee, int localSlope);
 
 autoDTW Matrices_to_DTW (Matrix me, Matrix thee, int matchStart, int matchEnd, int slope, double metric);
 
diff --git a/dwtools/DataModeler.cpp b/dwtools/DataModeler.cpp
index ea94ef4..5b8b5c5 100644
--- a/dwtools/DataModeler.cpp
+++ b/dwtools/DataModeler.cpp
@@ -1186,7 +1186,7 @@ static void FormantModeler_getCumulativeChiScores (FormantModeler me, int useSig
 			}
 		}
 	} catch (MelderError) {
-		Melder_throw (me, U"cannot determine cummulative chi squares.");
+		Melder_throw (me, U"cannot determine cumulative chi squares.");
 	}
 }
 
diff --git a/dwtools/Makefile b/dwtools/Makefile
index bc9c1f9..0f52e3f 100644
--- a/dwtools/Makefile
+++ b/dwtools/Makefile
@@ -1,10 +1,10 @@
 # Makefile of the library "dwtools"
 # David Weenink and Paul Boersma
-# 21 April 2017
+# 8 June 2017
 
 include ../makefile.defs
 
-CPPFLAGS = -I ../num -I ../LPC -I ../fon -I ../sys -I ../stat -I ../dwsys -I ../external/portaudio -I ../external/espeak -I ../EEG -I ../kar
+CPPFLAGS = -I ../num -I ../kar -I ../LPC -I ../fon -I ../sys -I ../stat -I ../dwsys -I ../external/portaudio -I ../external/espeak -I ../EEG -I ../kar
 
 OBJECTS = ActivationList.o AffineTransform.o \
 	Categories.o CategoriesEditor.o \
@@ -48,7 +48,7 @@ OBJECTS = ActivationList.o AffineTransform.o \
 	Table_extensions.o TableOfReal_and_SVD.o\
 	TableOfReal_extensions.o \
 	TableOfReal_and_Permutation.o \
-	TextGrid_extensions.o \
+	TextGrid_and_DurationTier.o TextGrid_extensions.o \
 	VowelEditor.o \
 	praat_MDS_init.o praat_BSS_init.o praat_HMM_init.o \
 	praat_KlattGrid_init.o praat_DataModeler_init.o praat_David_init.o
diff --git a/dwtools/Sound_extensions.cpp b/dwtools/Sound_extensions.cpp
index 6877ebc..6eeea6d 100644
--- a/dwtools/Sound_extensions.cpp
+++ b/dwtools/Sound_extensions.cpp
@@ -1,6 +1,6 @@
 /* Sound_extensions.cpp
  *
- * Copyright (C) 1993-2011, 2015-2016 David Weenink
+ * Copyright (C) 1993-2011, 2015-2017 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -66,7 +66,7 @@
 #include "DurationTier.h"
 #include "Ltas.h"
 #include "Manipulation.h"
-#include "NUM2.h"
+#include "NUMcomplex.h"
 
 
 #define MAX_T  0.02000000001   /* Maximum interval between two voice pulses (otherwise voiceless). */
@@ -645,8 +645,10 @@ autoSound Sound_createGammaTone (double minimumTime, double maximumTime, double
 		for (long i = 1; i <= my nx; i++) {
 			double t = (i - 0.5) * my dx;
 			double f = frequency + addition / (NUM2pi * t);
-			if (f > 0 && f < samplingFrequency / 2) my z[1][i] = pow (t, gamma - 1) *
-				        exp (- NUM2pi * bandwidth * t) * cos (NUM2pi * frequency * t + addition * log (t) + initialPhase);
+			if (f > 0 && f < samplingFrequency / 2) {
+				my z[1][i] = pow (t, gamma - 1.0) * exp (- NUM2pi * bandwidth * t) * 
+					cos (NUM2pi * frequency * t + addition * log (t) + initialPhase);
+			}
 		}
 		if (scaleAmplitudes) {
 			Vector_scale (me.get(), 0.99996948);
@@ -791,8 +793,7 @@ static void NUMgammatoneFilter4 (double *x, double *y, long n, double centre_fre
 		       - b[5] * y[i - 5] - b[6] * y[i - 6] - b[7] * y[i - 7] - b[8] * y[i - 8];
 	}
 }
-
-
+#if 0
 autoSound Sound_filterByGammaToneFilter4 (Sound me, double centre_frequency, double bandwidth) {
 	try {
 		if (centre_frequency <= 0) {
@@ -823,6 +824,32 @@ autoSound Sound_filterByGammaToneFilter4 (Sound me, double centre_frequency, dou
 		Melder_throw (U"Sound not filtered by gammatone filter4.");
 	}
 }
+#endif
+
+
+autoSound Sound_filterByGammaToneFilter4 (Sound me, double centre_frequency, double bandwidth) {
+	return Sound_filterByGammaToneFilter (me, centre_frequency, bandwidth, 4, 0.0);
+}
+
+autoSound Sound_filterByGammaToneFilter (Sound me, double centre_frequency, double bandwidth, long gamma, double initialPhase) {
+	try {
+		autoSound gammaTone = Sound_createGammaTone (my xmin, my xmax, 1.0 / my dx, gamma, centre_frequency, bandwidth, initialPhase, 0.0, 0);
+		// kSounds_convolve_scaling_INTEGRAL, SUM, NORMALIZE, PEAK_099
+		autoSound thee = Sounds_convolve (me, gammaTone.get(), kSounds_convolve_scaling_INTEGRAL, kSounds_convolve_signalOutsideTimeDomain_ZERO);
+		
+		double response_re, response_im;
+		gammaToneFilterResponseAtResonance (centre_frequency, bandwidth, gamma, initialPhase, my xmax - my xmin, & response_re, & response_im);
+		
+		double scale = 1.0 / sqrt (response_re * response_re + response_im * response_im);
+		for (long i = 1; i <= thy nx; i++) {
+			thy z[1][i] *= scale;
+		}
+		return thee;
+	} catch (MelderError) {
+		Melder_throw (U"Sound not filtered by gammatone filter4.");
+	}
+}
+
 
 /*
 Sound Sound_createShepardTone (double minimumTime, double maximumTime, double samplingFrequency,
@@ -1831,7 +1858,7 @@ static void _Sound_getWindowExtrema (Sound me, double *tmin, double *tmax, doubl
 	 We want to find the point in this interval where the formula switches from true to false.
 	 The x-value of the best point is approximated by a number of bisections.
 	 It is essential that the intermediate interpolated y-values are always between the values at points isample and isample+1.
-	 We cannot use a sinc-interpolation because at strong amplitude changes high-frequency oscilations may occur.
+	 We cannot use a sinc-interpolation because at strong amplitude changes high-frequency oscillations may occur.
 	 (may be leave out the interpolation and just use Vector_VALUE_INTERPOLATION_LINEAR only?)
 */
 static void Sound_findIntermediatePoint_bs (Sound me, long ichannel, long isample, bool left, bool right, const char32 *formula,
@@ -1843,7 +1870,7 @@ static void Sound_findIntermediatePoint_bs (Sound me, long ichannel, long isampl
 		*x = Matrix_columnToX (me, isample + 1);
 		*y = my z[ichannel][isample + 1];
 	}
-	if ( (left && right) || (!left && !right)) {
+	if (left == right) {
 		Melder_throw (U"Invalid situation.");
 	}
 
@@ -1852,40 +1879,36 @@ static void Sound_findIntermediatePoint_bs (Sound me, long ichannel, long isampl
 	}
 
 	long nx = 3;
-	double dx = my dx / 2;
+	double dx = my dx / 2.0;
 	double xleft = Matrix_columnToX (me, isample);
 	autoSound thee = Sound_create (my ny, my xmin, my xmax, nx, dx, xleft); // my domain !
 
 	for (long channel = 1; channel <= my ny; channel++) {
 		thy z[channel][1] = my z[channel][isample]; thy z[channel][3] = my z[channel][isample + 1];
 	}
-
-	Formula_compile (interpreter, thee.get(), formula, kFormula_EXPRESSION_TYPE_NUMERIC, true);
-
 	// bisection to find optimal x and y
 	long istep = 1;
 	double xright = xleft + my dx, xmid; // !!
 	do {
-		xmid = (xleft + xright) / 2;
+		xmid = (xleft + xright) / 2.0;
 
 		for (long channel = 1; channel <= my ny; channel++) {
 			thy z[channel][2] = Vector_getValueAtX (me, xmid, channel, interpolation);
 		}
-
-		// Only thy x1 and thy dx have changed; It seems we don't have to recompile.
 		struct Formula_Result result;
+		Formula_compile (interpreter, thee.get(), formula, kFormula_EXPRESSION_TYPE_NUMERIC, true);
 		Formula_run (ichannel, 2, & result);
 		bool current = (result.result.numericResult != 0.0);
 
-		dx /= 2;
-		if ((left && current) || (! left && ! current)) {
+		dx /= 2.0;
+		if (left == current) {
 			xleft = xmid;
 			left = current;
 			for (long channel = 1; channel <= my ny; channel++) {
 				thy z[channel][1] = thy z[channel][2];
 			}
 			thy x1 = xleft;
-		} else if ((left && ! current) || (!left && current)) {
+		} else if (left != current) { // xor of booleans!
 			xright = xmid;
 			right = current;
 			for (long channel = 1; channel <= my ny; channel++) {
@@ -1896,8 +1919,8 @@ static void Sound_findIntermediatePoint_bs (Sound me, long ichannel, long isampl
 			break;
 		}
 
-		thy xmin = xleft - dx / 2;
-		thy xmax = xright + dx / 2;
+		thy xmin = xleft - dx / 2.0;
+		thy xmax = xright + dx / 2.0;
 		thy dx = dx;
 		istep ++;
 	} while (istep < numberOfBisections);
diff --git a/dwtools/Sound_extensions.h b/dwtools/Sound_extensions.h
index abf5a60..261d980 100644
--- a/dwtools/Sound_extensions.h
+++ b/dwtools/Sound_extensions.h
@@ -96,6 +96,8 @@ autoSound Sound_createFromWindowFunction (double effectiveTime, double samplingF
 
 autoSound Sound_filterByGammaToneFilter4 (Sound me, double centre_frequency, double bandwidth);
 
+autoSound Sound_filterByGammaToneFilter (Sound me, double centre_frequency, double bandwidth, long gamma, double initialPhase);
+
 void Sounds_multiply (Sound me, Sound thee);
 /* precondition: my nx = thy nx */
 
diff --git a/dwtools/Table_extensions.cpp b/dwtools/Table_extensions.cpp
index 572a323..596c292 100644
--- a/dwtools/Table_extensions.cpp
+++ b/dwtools/Table_extensions.cpp
@@ -3376,7 +3376,7 @@ double Table_getMedianAbsoluteDeviation (Table me, long columnNumber)
 			data[irow] = row -> cells[columnNumber].number;
 			if (data[irow] == NUMundefined) {
 				Melder_throw (me, U": the cell in row ", irow, U" of column \"",
-					my columnHeaders[columnNumber].label ? my columnHeaders[columnNumber].label : Melder_integer (columnNumber), U" is undefined.");
+					my columnHeaders[columnNumber].label ? my columnHeaders[columnNumber].label : Melder_integer (columnNumber), U"\" is undefined.");
 			}
 		}
 		double mad, location;
diff --git a/dwtools/TextGrid_and_DurationTier.cpp b/dwtools/TextGrid_and_DurationTier.cpp
new file mode 100644
index 0000000..db9b171
--- /dev/null
+++ b/dwtools/TextGrid_and_DurationTier.cpp
@@ -0,0 +1,100 @@
+/* TextGrid_and_DurationTier.cpp
+ *
+ * Copyright (C) 2017 David Weenink
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TextGrid_and_DurationTier.h"
+#include "Thing.h"
+
+void IntervalTier_and_DurationTier_scaleTimes (IntervalTier me, DurationTier thee) {
+	if (my xmin != thy xmin || my xmax != thy xmax) {
+		Melder_throw (U"The domains of the IntervalTier and the DurationTier must be equal.");
+	}
+	double xmax_new = my xmin + RealTier_getArea (thee, my xmin, my xmax);
+	for (long i = 1; i <= my intervals.size; i ++) {
+		TextInterval segment = my intervals.at [i];
+		double xmin = RealTier_getArea (thee, my xmin, segment -> xmin);
+		double xmax = RealTier_getArea (thee, my xmin, segment -> xmax);
+		segment -> xmin = my xmin + xmin;
+		segment -> xmax = my xmin + xmax;
+	}
+	my xmax = xmax_new;
+}
+
+void TextTier_and_DurationTier_scaleTimes (TextTier me, DurationTier thee) {
+	if (my xmin != thy xmin || my xmax != thy xmax) {
+		Melder_throw (U"The domains of the TextTier and the DurationTier must be equal.");
+	}
+	double xmax_new = my xmin + RealTier_getArea (thee, my xmin, my xmax);
+	for (long ipoint = 1; ipoint <= my points.size; ipoint++) {
+		TextPoint point = my points.at [ipoint];
+		double time = RealTier_getArea (thee, my xmin, point -> number);
+		point -> number = time;
+	}
+	my xmax = xmax_new;
+}
+
+autoTextGrid TextGrid_and_DurationTier_scaleTimes (TextGrid me, DurationTier thee) {
+	try {
+		if (my xmin != thy xmin || my xmax != thy xmax) {
+			Melder_throw (U"The domains of the TextGrid and the DurationTier must be equal.");
+		}
+		double xmax_new = my xmin + RealTier_getArea (thee, my xmin, my xmax);
+		autoTextGrid him = Data_copy (me);
+		long numberOfTiers = my tiers -> size;
+		for (long itier = 1; itier <= numberOfTiers; itier++) {
+			Function anyTier = his tiers->at [itier];
+			if (anyTier -> classInfo == classIntervalTier) {
+				IntervalTier tier = static_cast <IntervalTier> (anyTier);
+				IntervalTier_and_DurationTier_scaleTimes (tier, thee);
+			} else { 
+				TextTier textTier = static_cast <TextTier> (anyTier);
+				TextTier_and_DurationTier_scaleTimes (textTier, thee);
+			}
+		}
+		his xmax = xmax_new;
+		return him;
+		
+	} catch (MelderError) {
+		Melder_throw (me, U": no time-scaled TextGrid created.");
+	}
+}
+
+autoDurationTier TextGrid_to_DurationTier (TextGrid me, long tierNumber, double timeScalefactor, double leftTransitionDuration, double rightTransitionDuration, int which_Melder_STRING, const char32 *criterion) {
+	try {
+		autoDurationTier him = DurationTier_create (my xmin, my xmax);
+		IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
+		for (long i = 1; i <= tier ->intervals.size; i++) {
+			TextInterval segment = tier -> intervals.at [i];
+			if (Melder_stringMatchesCriterion (segment -> text, which_Melder_STRING, criterion)) {
+				double xmin = segment -> xmin, xmax = segment -> xmax;
+				RealTier_addPoint (him.get(), xmin, 1.0);
+				RealTier_addPoint (him.get(), xmin + leftTransitionDuration, timeScalefactor);
+				RealTier_addPoint (him.get(), xmax - rightTransitionDuration, timeScalefactor);
+				RealTier_addPoint (him.get(), xmax, 1.0);	
+			}
+		}
+		long index = tier ->intervals.size;
+		if (index == 0) {
+			RealTier_addPoint (him.get(), my xmin, 1.0);
+		}
+		return him;
+	} catch (MelderError) {
+		Melder_throw (me, U":cannot create DurationTier.");
+	}
+}
+
+/* End of file TextGrid_and_DurationTier.cpp */
diff --git a/dwtools/TextGrid_and_DurationTier.h b/dwtools/TextGrid_and_DurationTier.h
new file mode 100644
index 0000000..d59d7c3
--- /dev/null
+++ b/dwtools/TextGrid_and_DurationTier.h
@@ -0,0 +1,33 @@
+#ifndef _TextGrid_and_DurationTier_h_
+#define _TextGrid_and_DurationTier_h_
+/* TextGrid_and_DurationTier.h
+ *
+ * Copyright (C) 2017 David Weenink
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TextGrid.h"
+#include "DurationTier.h"
+
+/* Scale the times of the TextGrid with the DurationTier */
+void IntervalTier_and_DurationTier_scaleTimes (IntervalTier me, DurationTier thee);
+
+void TextTier_and_DurationTier_scaleTimes (TextTier me, DurationTier thee);
+
+autoTextGrid TextGrid_and_DurationTier_scaleTimes (TextGrid me, DurationTier thee);
+
+autoDurationTier TextGrid_to_DurationTier (TextGrid me, long tierNumber, double timeScalefactor, double leftTransitionDuration, double rightTransitionDuration, int which_Melder_STRING, const char32 *criterion);
+
+#endif
diff --git a/dwtools/TextGrid_extensions.cpp b/dwtools/TextGrid_extensions.cpp
index 0774184..815b4ca 100644
--- a/dwtools/TextGrid_extensions.cpp
+++ b/dwtools/TextGrid_extensions.cpp
@@ -808,4 +808,12 @@ autoTextGrid TextGrids_to_TextGrid_appendContinuous (OrderedOf<structTextGrid>*
 	}
 }
 
+void NUMshift (double *x, double dx) {
+	*x += dx;
+}
+
+autoIntervalTier IntervalTier_shiftBoundaries (IntervalTier me, double startTime, double shiftTime) {
+	
+}
+
 /* End of file TextGrid_extensions.cpp */
diff --git a/dwtools/manual_dwtools.cpp b/dwtools/manual_dwtools.cpp
index 25b83c4..03aa0ad 100644
--- a/dwtools/manual_dwtools.cpp
+++ b/dwtools/manual_dwtools.cpp
@@ -1,6 +1,6 @@
 /* manual_dwtools.cpp
  *
- * Copyright (C) 1993-2016 David Weenink
+ * Copyright (C) 1993-2017 David Weenink
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -2410,6 +2410,21 @@ NORMAL (U"form the eigenvectors. The important eigenvectors, of course, correspo
 	"to the positions where the %l eigenvalues are not infinite.")
 MAN_END
 
+MAN_BEGIN (U"Get incomplete gamma...", U"djmw", 20170531)
+INTRO (U"Get the value of the @@incomplete gamma function@ for particular values of \\al and %x.")
+ENTRY (U"Algorithm")
+NORMAL (U"The algorithm is taken from @@Kostlan & Gokhman (1987)@.")
+MAN_END
+
+MAN_BEGIN (U"incomplete gamma function", U"djmw", 20170531)
+INTRO (U"The incomplete gamma function is defined as:")
+FORMULA (U"\\Ga(\\al, %x) = \\in__%x_^^\\oo^ %t^^\\al\\-m1^e^^-%t^dt, \\Ga(\\al) = \\Ga(\\al, 0),")
+NORMAL (U"where \\al and %x are complex numbers and Re(\\al) > 0.")
+NORMAL (U"The complementary incomplete gamma function is defined as:")
+FORMULA (U"\\ga(\\al, %x) = \\in__%0_^^%x^ %t^^\\al\\-m1^e^^-%t^dt = \\Ga(\\al)\\-m\\Ga(\\al, %x).")
+
+MAN_END
+
 MAN_BEGIN (U"invFisherQ", U"djmw", 20000525)
 INTRO (U"$$invFisherQ$ (%q, %df1, %df2) returns the value %f for which "
 	"$$@fisherQ (%f, %df1, %df2) = %q.")
@@ -4939,6 +4954,91 @@ NORMAL (U"We add an extra (empty) interval into each %%interval tier%. "
 NORMAL (U"For %%point tiers% only the domain will be changed.")
 MAN_END
 
+MAN_BEGIN (U"TextGrid & DurationTier: To TextGrid (scale times)", U"djmw", 20170612)
+INTRO (U"Scales the durations of the selected @@TextGrid@ intervals as specified by the selected @@DurationTier at .")
+
+MAN_END
+
+MAN_BEGIN (U"TextGrid: To DurationTier...", U"djmw", 20170617)
+INTRO (U"Returns a @@DurationTier@ that could scale the durations of the specified intervals of the selected @@TextGrid@ with a specified factor.")
+ENTRY (U"Settings")
+SCRIPT (6, Manual_SETTINGS_WINDOW_HEIGHT (5), U""
+	Manual_DRAW_SETTINGS_WINDOW ("TextGrid: To DurationTier", 5)
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Tier number", "1")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD("Time scale factor", "2.0")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD("Left transition duration", "1e-10")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD("Right transition duration", "1e-10")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("Scale intervals whose labels", "starts with")
+	Manual_DRAW_SETTINGS_WINDOW_FIELD ("...the text", "hi")
+)
+TAG (U"##Tier number#")
+DEFINITION (U"specifies the tier with the intervals.")
+TAG (U"##Time scale factor")
+DEFINITION (U"specifies the scale factor by which the duration of a selected interval has to be multiplied.")
+TAG (U"##Left transition duration#")
+DEFINITION (U"specifies how long it takes to go from a time scale factor of 1.0 to the specified one. Default a very small duration is used. ")
+TAG (U"##Right transition duration#")
+DEFINITION (U"specifies the time it takes to go from the specified time scale factor to 1.0. Default a very small duration is used.")
+TAG (U"##Scale intervals whose labels")
+DEFINITION (U"specifies the interval selection criterion.")
+TAG (U"##...the text")
+DEFINITION (U"specifies the text used in the selection criterion.")
+ENTRY (U"Algorithm")
+SCRIPT (5, 3, U"ymin = 0.9\n"
+	"Axes: 0, 1, ymin, 2.0\n" 
+	"t1 = 0.2\n"
+	"t4 = 0.9\n"
+	"timeScaleFactor = 1.5\n"
+	"leftTransitionDuration = 0.1\n"
+	"rightTransitionDuration = 0.2\n"
+	"t2 = t1 + leftTransitionDuration\n"
+	"t3 = t4 - rightTransitionDuration\n"
+	"Solid line\n"
+	"Draw line: t1, 1, t2, timeScaleFactor\n"
+	"Draw line: t2, timeScaleFactor, t3, timeScaleFactor\n"
+	"Draw line: t3, timeScaleFactor, t4, 1.0\n"
+	"Dotted line\n"
+	"Draw line: 0, 1, t1, 1\n"
+	"Draw line: t4, 1.0, 1.0, 1.0\n"
+	"Draw line: t1, ymin, t1, timeScaleFactor+0.1\n"
+	"Draw line: t2, ymin, t2, timeScaleFactor+0.1\n"
+	"Draw two-way arrow: t1, timeScaleFactor+0.1, t2, timeScaleFactor+0.1\n"
+	"Text: (t1+t2)/2, \"Centre\", timeScaleFactor+0.1, \"Bottom\", \"leftTransitionDuration\"\n"
+	"Draw line: t3, ymin, t3, timeScaleFactor+0.1\n"
+	"Draw line: t4, ymin, t4, timeScaleFactor+0.1\n"
+	"Draw two-way arrow: t3, timeScaleFactor+0.1, t4, timeScaleFactor+0.1\n"
+	"Text: (t3+t4)/2, \"Centre\", timeScaleFactor+0.1, \"Bottom\", \"rightTransitionDuration\"\n"
+	"One mark bottom: t1, \"no\", \"yes\", \"no\", \"t__1_\"\n"
+	"One mark bottom: t2, \"no\", \"yes\", \"no\", \"t__2_\"\n"
+	"One mark bottom: t3, \"no\", \"yes\", \"no\", \"t__3_\"\n"
+	"One mark bottom: t4, \"no\", \"yes\", \"no\", \"t__4_\"\n"
+	"One mark left: 1.0, \"yes\", \"yes\", \"no\", \"\"\n"
+	"Text bottom: \"yes\", \"Time (s) \\->\"\n"
+	"Text left: \"yes\", \"Duration scale factor \\->\"\n"
+	"Draw inner box\n"
+)
+NORMAL (U"For each selected interval its duration will be specified by four points in the duration tier as the figure above shows. "
+	"Given that the start time "
+	"and the end time of the interval are at %t__1_ and %t__4_, respectively, the times of these four points will be "
+	"%t__1_, %t__2_=%t__1_+%%leftTransitionDuration%, %t__3_=%t__4_-%%rightTransitionDuration% and %t__4_. The associated duration scale factors "
+	"will be 1.0, %%timeScalefactor%, %%timeScalefactor% and 1.0, respectively.")
+NORMAL (U"Normally we would use very small values for the right and the left transition durations and the curve in the figure above "
+	"would look more like a rectangular block instead of the trapezium above. If, on the contrary, larger values for the durations are taken, such that the sum of "
+	"the left and the right transition durations %%exceeds% the interval's width, then the ordering of the time points at %t__1_ to %t__4_ changes "
+	"which will have unexpected results on the duration tier.")
+ENTRY (U"Examples")
+NORMAL (U"Suppose you want to change the durations of some parts in a sound. The way to go is:")
+LIST_ITEM (U"1. Create a TextGrid with at least one interval tier with the segments of interest labeled.")
+LIST_ITEM (U"2. Select the TextGrid and choose the ##To DurationTier...# option.")
+LIST_ITEM (U"3. Use the selected sound to create a @@Manipulation@ object from it. Check and eventually correct the pitch measurements in this object (##View & Edit#) as the quality of the resynthesis depends critically on the quality of the pitch measurements.")
+LIST_ITEM (U"4. Select the Manipulation object and the newly created DurationTier object together and choose ##Replace duration tier#.")
+LIST_ITEM (U"5. Select the Manipulation object and choose ##Get resynthesis (overlap-add)#. The newly created sound object will have the "
+	"durations of its selected intervals changed.")
+LIST_ITEM (U"6. Optionally you might also want to scale the TextGrid to line up with the newly created sound too. You can do so by selecting the "
+	"TextGrid and the DurationTier together and choose ##To TextGrid (scale times)#. You will get a new TextGrid that is nicely "
+	"aligned with the new sound.")
+MAN_END
+
 MAN_BEGIN (U"TIMIT acoustic-phonetic speech corpus", U"djmw", 19970320)
 INTRO (U"A large American-English speech corpus that resulted from the joint efforts "
 	"of several American research sites.")
@@ -5036,10 +5136,10 @@ NORMAL (U"for 0 \\<_ %x \\<_ 1 and %a and %b and %a+%b not equal to a negative i
 //Symmetry: $I_x(a,b) = 1 - I_{1-x}(b,a)$
 MAN_END
 
-MAN_BEGIN (U"incompleteGammaP", U"djmw", 20071024)
+MAN_BEGIN (U"incompleteGammaP", U"djmw", 20170531)
 TAG (U"##incompleteGammaP (%a, %x)")
 DEFINITION (U"incompleteGammaP = 1/\\Ga(%a)\\in__0_^%x e^^-%t^%t^^%a-1^ dt,")
-NORMAL (U"For %x\\>_ 0 and %a not a negative integer.")
+NORMAL (U"where %x and %a are real numbers that satisfy %x\\>_ 0 and %a not being a negative integer.")
 MAN_END
 
 MAN_BEGIN (U"lnBeta", U"djmw", 20071024)
@@ -5143,7 +5243,7 @@ NORMAL (U"P.I.M. Johannesma (1972): \"The pre-response stimulus ensemble of "
 MAN_END
 
 MAN_BEGIN (U"Johnson (1998)", U"djmw", 20000525)
-NORMAL (U"D.E. Johnson (1998): %%Applied Multivariate methods%.")
+NORMAL (U"D.E. Johnson (1998): %%Applied multivariate methods%.")
 MAN_END
 
 MAN_BEGIN (U"Keating & Esposito (2006)", U"djmw", 20130620)
@@ -5159,8 +5259,12 @@ MAN_BEGIN (U"Kim & Kim (2006)", U"djmw", 20110617)
 NORMAL (U"D.H. Kim & M.-J. Kim (2006): \"An extension of polygon clipping to resolve degenerate cases.\" %%Computer-Aided Design & Applications% #3: 447\\--456.")
 MAN_END
 
+MAN_BEGIN (U"Kostlan & Gokhman (1987)", U"djmw", 20170530)
+NORMAL (U"E. Kostlan & D. Gokhman (1987): \"A program for calculating the incomplete gamma function.\" %%Technical report, Dept. of Mathematics, Univ. of California, Berkeley, 1987.")
+MAN_END
+
 MAN_BEGIN (U"Krishnamoorthy & Yu (2004)", U"djmw", 20090813)
-NORMAL (U"K. Krishnamoortht & J. Yu (2004): \"Modified Nel and Van der Merwe test for multivariate "
+NORMAL (U"K. Krishnamoorthy & J. Yu (2004): \"Modified Nel and Van der Merwe test for multivariate "
 	"Behrens-Fisher problem.\" %%Statistics & Probability Letters% #66: 161\\--169.")
 MAN_END
 
@@ -5172,7 +5276,7 @@ NORMAL (U"L.F. Lamel, R.H. Kassel & S. Sennef (1986): \"Speech Database "
 MAN_END
 
 MAN_BEGIN (U"Morrison (1990)", U"djmw", 19980123)
-NORMAL (U"D.F. Morrison (1990): %%Multivariate Statistical Methods%. "
+NORMAL (U"D.F. Morrison (1990): %%Multivariate statistical methods%. "
 	"New York: McGraw-Hill.")
 MAN_END
 
diff --git a/dwtools/praat_DataModeler_init.cpp b/dwtools/praat_DataModeler_init.cpp
index 1cc801a..d5ba17b 100644
--- a/dwtools/praat_DataModeler_init.cpp
+++ b/dwtools/praat_DataModeler_init.cpp
@@ -975,19 +975,12 @@ DO
 
 DIRECT (WINDOW_OptimalCeilingTier_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit an OptimalCeilingTier from batch.");
-	Sound sound = nullptr;
-	LOOP {
-		if (CLASS == classSound) {
-			sound = (Sound) OBJECT;   // may stay nullptr
-		}
-	}
-	LOOP if (CLASS == classOptimalCeilingTier) {
-		iam (OptimalCeilingTier);
-		autoOptimalCeilingTierEditor editor = OptimalCeilingTierEditor_create (ID_AND_FULL_NAME, me, sound, true);
+	FIND_TWO_WITH_IOBJECT (OptimalCeilingTier, Sound)   // Sound may be null
+		autoOptimalCeilingTierEditor editor = OptimalCeilingTierEditor_create (ID_AND_FULL_NAME, me, you, true);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 
 /*************************** PitchModeler *************************************/
diff --git a/dwtools/praat_David_init.cpp b/dwtools/praat_David_init.cpp
index 44d54e1..1a28d64 100644
--- a/dwtools/praat_David_init.cpp
+++ b/dwtools/praat_David_init.cpp
@@ -1,6 +1,6 @@
 /* praat_David_init.cpp
  *
- * Copyright (C) 1993-2016 David Weenink, 2015 Paul Boersma
+ * Copyright (C) 1993-2017 David Weenink, 2015 Paul Boersma
  *
  * This code is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -63,7 +63,7 @@
  djmw 20120813 Latest modification.
 */
 
-#include "NUM2.h"
+#include "NUMcomplex.h"
 #include "NUMlapack.h"
 #include "NUMmachar.h"
 
@@ -129,6 +129,7 @@
 #include "Sound_to_Pitch2.h"
 #include "Sound_to_SPINET.h"
 #include "TableOfReal_and_SVD.h"
+#include "TextGrid_and_DurationTier.h"
 #include "VowelEditor.h"
 
 #include "praat_TimeFrameSampled.h"
@@ -1526,7 +1527,7 @@ DO
 	MODIFY_FIRST_OF_TWO_END
 }
 
-FORM (NEW1_DTW_and_Polygon_to_Matrix_cummulativeDistances, U"DTW & Polygon: To Matrix (cumm. distances)", nullptr) {
+FORM (NEW1_DTW_and_Polygon_to_Matrix_cumulativeDistances, U"DTW & Polygon: To Matrix (cum. distances)", nullptr) {
     RADIOVAR (slopeConstraint, U"Slope constraint", 1)
 		RADIOBUTTON (U"no restriction")
 		RADIOBUTTON (U"1/3 < slope < 3")
@@ -1535,7 +1536,7 @@ FORM (NEW1_DTW_and_Polygon_to_Matrix_cummulativeDistances, U"DTW & Polygon: To M
     OK
 DO
     CONVERT_TWO (DTW, Polygon)
-		autoMatrix result = DTW_and_Polygon_to_Matrix_cummulativeDistances (me, you, slopeConstraint);
+		autoMatrix result = DTW_and_Polygon_to_Matrix_cumulativeDistances (me, you, slopeConstraint);
      CONVERT_TWO_END (my name, U"_", slopeConstraint);
 }
 
@@ -1890,7 +1891,7 @@ DO
 	MODIFY_EACH_END
 }
 
-FORM (NEW_DTW_to_Matrix_cummulativeDistances, U"DTW: To Matrix", nullptr) {
+FORM (NEW_DTW_to_Matrix_cumulativeDistances, U"DTW: To Matrix", nullptr) {
     REALVAR (sakoeChibaBand, U"Sakoe-Chiba band (s)", U"0.05")
     RADIOVAR (slopeConstraint, U"Slope constraint", 1)
 		RADIOBUTTON (U"no restriction")
@@ -1900,7 +1901,7 @@ FORM (NEW_DTW_to_Matrix_cummulativeDistances, U"DTW: To Matrix", nullptr) {
     OK
 DO
     CONVERT_EACH (DTW)
-        autoMatrix result = DTW_to_Matrix_cummulativeDistances (me, sakoeChibaBand, slopeConstraint);
+        autoMatrix result = DTW_to_Matrix_cumulativeDistances (me, sakoeChibaBand, slopeConstraint);
 	CONVERT_EACH_END (my name, U"_cd")
 }
 
@@ -4786,7 +4787,7 @@ DO
 	INFO_ONE (Roots)
 		dcomplex z = Roots_getRoot (me, rootNumber);
 		MelderInfo_open ();
-			MelderInfo_writeLine (z.re, z.im,  U" i");
+			MelderInfo_writeLine (z.re, U"+", z.im,  U"i");
 		MelderInfo_close ();
 	INFO_ONE_END
 }
@@ -4883,6 +4884,18 @@ DO
 	Melder_information (result, U" (inv tukeyQ)");
 END }
 
+FORM (COMPLEX_Praat_getIncompleteGamma, U"Get incomplete gamma", U"Get incomplete gamma...") {
+	POSITIVEVAR (reAlpha, U"Real part of alpha", U"4.0")
+	REALVAR (imAlpha, U"Imaginary part of alpha", U"0.0")
+	REALVAR (reX, U"Real part of X", U"4.0")
+	REALVAR (imX, U"Imaginary part of X", U"0.0")
+	OK
+DO
+	double result_re, result_im;
+	NUMincompleteGammaFunction (reAlpha, imAlpha, reX, imX, & result_re, & result_im);
+	Melder_information (result_re, U"+", result_im, U"i");
+END }
+
 /******************** Sound ****************************************/
 
 #define Sound_create_addCommonFields(startTime,endTime,samplingFrequency) \
@@ -6986,6 +6999,26 @@ DO
 	CONVERT_COUPLE_END (my name, U"_", your name);
 }
 
+FORM (NEW_TextGrid_to_DurationTier, U"TextGrid: To DurationTier", U"TextGrid: To DurationTier...") {
+	NATURALVAR (tierNumber, U"Tier number", U"1")
+	POSITIVEVAR (timeScaleFactor, U"Time scale factor", U"2.0")
+	POSITIVEVAR (leftTransitionDuration, U"Left transition duration (s)", U"1e-10")
+	POSITIVEVAR (rightTransitionDuration, U"Right transition duration (s)", U"1e-10")
+	OPTIONMENU_ENUM4 (___, U"Scale intervals whose label ", kMelder_string, DEFAULT)
+	SENTENCE4 (___theText, U"...the text", U"hi")
+	OK
+DO
+	CONVERT_EACH (TextGrid)
+		autoDurationTier result = TextGrid_to_DurationTier (me,tierNumber, timeScaleFactor,leftTransitionDuration, rightTransitionDuration, ___, ___theText);
+	CONVERT_EACH_END (my name)
+}
+
+DIRECT (NEW_TextGrid_and_DurationTier_to_TextGrid) {
+	CONVERT_TWO (TextGrid, DurationTier)
+		autoTextGrid result = TextGrid_and_DurationTier_scaleTimes (me, you);
+	CONVERT_TWO_END (my name, U"_", your name)
+}
+
 FORM (NEW1_TextGrids_and_EditCostsTable_to_Table_textAlignmentment, U"TextGrids & EditCostsTable: To Table(text alignmentment)", nullptr) {
 	NATURALVAR (targetTierNumber, U"Target tier", U"1")
 	NATURALVAR (sourceTierNumber, U"Source tier", U"1")
@@ -7268,6 +7301,7 @@ void praat_uvafon_David_init () {
 	praat_addMenuCommand (U"Objects", U"Technical", U"Report floating point properties", U"Report integer properties", 0, INFO_Praat_ReportFloatingPointProperties);
 	praat_addMenuCommand (U"Objects", U"Goodies", U"Get TukeyQ...", 0, praat_HIDDEN, REAL_Praat_getTukeyQ);
 	praat_addMenuCommand (U"Objects", U"Goodies", U"Get invTukeyQ...", 0, praat_HIDDEN, REAL_Praat_getInvTukeyQ);
+	praat_addMenuCommand (U"Objects", U"Goodies", U"Get incomplete gamma...", 0, praat_HIDDEN, COMPLEX_Praat_getIncompleteGamma);
 //	praat_addMenuCommand (U"Objects", U"New", U"Create Strings as espeak voices", U"Create Strings as directory list...", praat_DEPTH_1 + praat_HIDDEN, NEW1_Strings_createAsEspeakVoices);
 	praat_addMenuCommand (U"Objects", U"New", U"Create iris data set", U"Create TableOfReal...", 1, NEW1_CreateIrisDataset);
 	praat_addMenuCommand (U"Objects", U"New", U"Create Permutation...", nullptr, 0, NEW_Permutation_create);
@@ -7577,7 +7611,7 @@ void praat_uvafon_David_init () {
     praat_addAction1 (classDTW, 0, U"Find path (band & slope)...", nullptr, 0, MODIFY_DTW_findPath_bandAndSlope);
     praat_addAction1 (classDTW, 0, U"To Polygon...", nullptr, 1, NEW_DTW_to_Polygon);
 	praat_addAction1 (classDTW, 0, U"To Matrix (distances)", nullptr, 0, NEW_DTW_to_Matrix_distances);
-    praat_addAction1 (classDTW, 0, U"To Matrix (cumm. distances)...", nullptr, 0, NEW_DTW_to_Matrix_cummulativeDistances);
+    praat_addAction1 (classDTW, 0, U"To Matrix (cum. distances)...", nullptr, 0, NEW_DTW_to_Matrix_cumulativeDistances);
 	praat_addAction1 (classDTW, 0, U"Swap axes", nullptr, 0, NEW_DTW_swapAxes);
 
 	praat_addAction2 (classDTW, 1, classMatrix, 1, U"Replace matrix", nullptr, 0, MODIFY_DTW_and_Matrix_replace);
@@ -7585,7 +7619,7 @@ void praat_uvafon_David_init () {
 	praat_addAction2 (classDTW, 1, classIntervalTier, 1, U"To Table (distances)", nullptr, 0, NEW1_DTW_and_IntervalTier_to_Table);
 
     praat_addAction2 (classDTW, 1, classPolygon, 1, U"Find path inside...", nullptr, 0, MODIFY_DTW_and_Polygon_findPathInside);
-    praat_addAction2 (classDTW, 1, classPolygon, 1, U"To Matrix (cumm. distances)...", nullptr, 0, NEW1_DTW_and_Polygon_to_Matrix_cummulativeDistances);
+    praat_addAction2 (classDTW, 1, classPolygon, 1, U"To Matrix (cum. distances)...", nullptr, 0, NEW1_DTW_and_Polygon_to_Matrix_cumulativeDistances);
 	praat_addAction2 (classDTW, 1, classSound, 2, U"Draw...", nullptr, 0, GRAPHICS_DTW_and_Sounds_draw);
 	praat_addAction2 (classDTW, 1, classSound, 2, U"Draw warp (x)...", nullptr, 0, GRAPHICS_DTW_and_Sounds_drawWarpX);
 
@@ -8081,6 +8115,9 @@ void praat_uvafon_David_init () {
 	praat_addAction1 (classTextGrid, 0, U"Replace interval text...", U"Set interval text...", 2, MODIFY_TextGrid_replaceIntervalTexts);
 	praat_addAction1 (classTextGrid, 0, U"Replace point text...", U"Set point text...", 2, MODIFY_TextGrid_replacePointTexts);
 	praat_addAction1 (classTextGrid, 2, U"To Table (text alignment)...", U"Extract part...", 0, NEW1_TextGrids_to_Table_textAlignmentment);
+	
+	praat_addAction1 (classTextGrid, 0, U"To DurationTier...", U"Concatenate", 0, NEW_TextGrid_to_DurationTier);
+	praat_addAction2 (classTextGrid, 1, classDurationTier, 1, U"To TextGrid (scale times)", nullptr, 0, NEW_TextGrid_and_DurationTier_to_TextGrid);
 	praat_addAction2 (classTextGrid, 2, classEditCostsTable, 1, U"To Table (text alignment)...", nullptr, 0, NEW1_TextGrids_and_EditCostsTable_to_Table_textAlignmentment);
 
 	INCLUDE_MANPAGES (manual_dwtools_init)
diff --git a/dwtools/praat_KlattGrid_init.cpp b/dwtools/praat_KlattGrid_init.cpp
index 009dd0b..fb9f373 100644
--- a/dwtools/praat_KlattGrid_init.cpp
+++ b/dwtools/praat_KlattGrid_init.cpp
@@ -178,13 +178,11 @@ DO
 #define KlattGrid_INSTALL_TIER_EDITOR(Name) \
 DIRECT (WINDOW_KlattGrid_edit##Name##Tier)  {\
 	if (theCurrentPraatApplication -> batch) { Melder_throw (U"Cannot edit a KlattGrid from batch."); } \
-	LOOP {\
-		iam (KlattGrid); \
+	FIND_ONE_WITH_IOBJECT (KlattGrid) \
 		auto##KlattGrid_##Name##TierEditor editor = KlattGrid_##Name##TierEditor_create (ID_AND_FULL_NAME, me); \
 		praat_installEditor (editor.get(), IOBJECT); \
 		editor.releaseToUser(); \
-	}\
-END }
+	END }
 
 KlattGrid_INSTALL_TIER_EDITOR (Pitch)
 KlattGrid_INSTALL_TIER_EDITOR (VoicingAmplitude)
diff --git a/external/gsl/gsl_rng__file.c b/external/gsl/gsl_rng__file.c
index f6d250f..6f80357 100644
--- a/external/gsl/gsl_rng__file.c
+++ b/external/gsl/gsl_rng__file.c
@@ -27,7 +27,7 @@ gsl_rng_fread (FILE * stream, gsl_rng * r)
 {
   size_t n = r->type->size ;
 
-  char * state = r->state;
+  char * state = (char *) r->state;
 
   size_t items = fread (state, 1, n, stream);
   
@@ -44,7 +44,7 @@ gsl_rng_fwrite (FILE * stream, const gsl_rng * r)
 {
   size_t n = r->type->size ;
 
-  char * state = r->state;
+  char * state = (char *) r->state;
   
   size_t items = fwrite (state, 1, n, stream);
   
diff --git a/fon/FunctionEditor.h b/fon/FunctionEditor.h
index e9233db..f9bfe31 100644
--- a/fon/FunctionEditor.h
+++ b/fon/FunctionEditor.h
@@ -39,9 +39,9 @@ Thing_define (FunctionEditor, Editor) {
 		/*    tmin <= (startSelection, endSelection) <= tmax; */
 
 	autoGraphics graphics;   // used in the 'draw' method
-	short functionViewerLeft, functionViewerRight;   // size of drawing areas in pixels
-	short selectionViewerLeft, selectionViewerRight;   // size of drawing areas in pixels
-	short height;   // size of drawing areas in pixels
+	int functionViewerLeft, functionViewerRight;   // size of drawing areas in pixels
+	int selectionViewerLeft, selectionViewerRight;   // size of drawing areas in pixels
+	int height;   // size of drawing areas in pixels
 	GuiText text;   // optional text at top
 	int shiftKeyPressed;   // information for the 'play' method
 	bool playingCursor, playingSelection;   // information for end of play
diff --git a/fon/RealTier.cpp b/fon/RealTier.cpp
index 63a5d49..4b61adf 100644
--- a/fon/RealTier.cpp
+++ b/fon/RealTier.cpp
@@ -1,6 +1,6 @@
 /* RealTier.cpp
  *
- * Copyright (C) 1992-2012,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2014,2015,2016,2017 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
@@ -152,7 +152,6 @@ double RealTier_getMinimumValue (RealTier me) {
 
 double RealTier_getArea (RealTier me, double tmin, double tmax) {
 	long n = my points.size, imin, imax;
-	//RealPoint *points = & my points [0];
 	if (n == 0) return NUMundefined;
 	if (n == 1) return (tmax - tmin) * my points.at [1] -> value;
 	imin = AnyTier_timeToLowIndex (me->asAnyTier(), tmin);
diff --git a/fon/Sound_and_Spectrum.cpp b/fon/Sound_and_Spectrum.cpp
index 8dcc20f..f7026d9 100644
--- a/fon/Sound_and_Spectrum.cpp
+++ b/fon/Sound_and_Spectrum.cpp
@@ -1,6 +1,6 @@
 /* Sound_and_Spectrum.cpp
  *
- * Copyright (C) 1992-2012,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2015,2016,2017 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 2006/12/31 compatible with stereo sounds
  * pb 2009/01/18 Interpreter argument to formula
  * pb 2011/06/06 C++
+ * pb 2017/06/07
  */
 
 #include "Sound_and_Spectrum.h"
@@ -36,18 +37,40 @@
 autoSpectrum Sound_to_Spectrum (Sound me, int fast) {
 	try {
 		long numberOfSamples = my nx;
+		const long numberOfChannels = my ny;
 		if (fast) {
 			numberOfSamples = 2;
 			while (numberOfSamples < my nx) numberOfSamples *= 2;
 		}
 		long numberOfFrequencies = numberOfSamples / 2 + 1;   // 4 samples -> cos0 cos1 sin1 cos2; 5 samples -> cos0 cos1 sin1 cos2 sin2
+
 		autoNUMvector <double> data (1, numberOfSamples);
+		if (numberOfChannels == 1) {
+			const double *channel = my z [1];
+			for (long i = 1; i <= my nx; i ++) {
+				data [i] = channel [i];
+			}
+			/*
+				All samples from `my nx + 1` through `numberOfSamples`
+				should be set to zero, but they are already zero.
+			*/
+			// so do nothing
+		} else {
+			for (long ichan = 1; ichan <= numberOfChannels; ichan ++) {
+				const double *channel = my z [ichan];
+				for (long i = 1; i <= my nx; i ++) {
+					data [i] += channel [i];
+				}
+			}
+			for (long i = 1; i <= my nx; i ++) {
+				data [i] /= numberOfChannels;
+			}
+		}
+
 		autoNUMfft_Table fourierTable;
 		NUMfft_Table_init (& fourierTable, numberOfSamples);
-
-		for (long i = 1; i <= my nx; i ++)
-			data [i] = my ny == 1 ? my z [1] [i] : 0.5 * (my z [1] [i] + my z [2] [i]);
 		NUMfft_forward (& fourierTable, data.peek());
+
 		autoSpectrum thee = Spectrum_create (0.5 / my dx, numberOfFrequencies);
 		thy dx = 1.0 / (my dx * numberOfSamples);   // override
 		double *re = thy z [1];
@@ -78,11 +101,11 @@ autoSound Spectrum_to_Sound (Spectrum me) {
 	try {
 		double *re = my z [1], *im = my z [2];
 		double lastFrequency = my x1 + (my nx - 1) * my dx;
-		int originalNumberOfSamplesProbablyOdd = im [my nx] != 0.0 || my xmax - lastFrequency > 0.25 * my dx;
+		bool originalNumberOfSamplesProbablyOdd = ( im [my nx] != 0.0 || my xmax - lastFrequency > 0.25 * my dx );
 		if (my x1 != 0.0)
 			Melder_throw (U"A Fourier-transformable Spectrum must have a first frequency of 0 Hz, not ", my x1, U" Hz.");
 		long numberOfSamples = 2 * my nx - ( originalNumberOfSamplesProbablyOdd ? 1 : 2 );
-		autoSound thee = Sound_createSimple (1, 1 / my dx, numberOfSamples * my dx);
+		autoSound thee = Sound_createSimple (1, 1.0 / my dx, numberOfSamples * my dx);
 		double *amp = thy z [1];
 		double scaling = my dx;
 		amp [1] = re [1] * scaling;
@@ -117,9 +140,9 @@ autoSpectrum Spectrum_lpcSmoothing (Spectrum me, int numberOfPeaks, double preem
 
 		long nfft = 2 * (thy nx - 1);
 		long ndata = numberOfCoefficients < nfft ? numberOfCoefficients : nfft - 1;
-		double scale = 10 * (gain > 0 ? sqrt (gain) : 1) / numberOfCoefficients;
+		double scale = 10.0 * (gain > 0.0 ? sqrt (gain) : 1.0) / numberOfCoefficients;
 		autoNUMvector <double> data (1, nfft);
-		data [1] = 1;
+		data [1] = 1.0;
 		for (long i = 1; i <= ndata; i ++)
 			data [i + 1] = a [i];
 		NUMrealft (data.peek(), nfft, 1);
@@ -130,10 +153,10 @@ autoSpectrum Spectrum_lpcSmoothing (Spectrum me, int numberOfPeaks, double preem
 		long halfnfft = nfft / 2;
 		for (long i = 2; i <= halfnfft; i ++) {
 			double real = data [i + i - 1], imag = data [i + i];
-			re [i] = scale / sqrt (real * real + imag * imag) / (1 + thy dx * (i - 1) / preemphasisFrequency);
-			im [i] = 0;
+			re [i] = scale / sqrt (real * real + imag * imag) / (1.0 + thy dx * (i - 1) / preemphasisFrequency);
+			im [i] = 0.0;
 		}
-		re [halfnfft + 1] = scale / data [2] / (1 + thy dx * halfnfft / preemphasisFrequency);
+		re [halfnfft + 1] = scale / data [2] / (1.0 + thy dx * halfnfft / preemphasisFrequency);
 		im [halfnfft + 1] = 0.0;
 		return thee;
 	} catch (MelderError) {
diff --git a/fon/Sound_to_Intensity.cpp b/fon/Sound_to_Intensity.cpp
index 40421c0..b243ce8 100644
--- a/fon/Sound_to_Intensity.cpp
+++ b/fon/Sound_to_Intensity.cpp
@@ -1,6 +1,6 @@
 /* Sound_to_Intensity.cpp
  *
- * Copyright (C) 1992-2011,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2011,2014,2015,2016,2017 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
@@ -33,7 +33,7 @@
 
 #include "Sound_to_Intensity.h"
 
-static autoIntensity Sound_to_Intensity_ (Sound me, double minimumPitch, double timeStep, int subtractMeanPressure) {
+static autoIntensity Sound_to_Intensity_ (Sound me, double minimumPitch, double timeStep, bool subtractMeanPressure) {
 	try {
 		/*
 		 * Preconditions.
@@ -48,16 +48,16 @@ static autoIntensity Sound_to_Intensity_ (Sound me, double minimumPitch, double
 		 */
 		if (timeStep == 0.0) timeStep = 0.8 / minimumPitch;   // default: four times oversampling Hanning-wise
 
-		double windowDuration = 6.4 / minimumPitch;
+		const double windowDuration = 6.4 / minimumPitch;
 		Melder_assert (windowDuration > 0.0);
-		double halfWindowDuration = 0.5 * windowDuration;
-		long halfWindowSamples = (long) floor (halfWindowDuration / my dx);
+		const double halfWindowDuration = 0.5 * windowDuration;
+		const long halfWindowSamples = (long) floor (halfWindowDuration / my dx);
 		autoNUMvector <double> amplitude (- halfWindowSamples, halfWindowSamples);
 		autoNUMvector <double> window (- halfWindowSamples, halfWindowSamples);
 
 		for (long i = - halfWindowSamples; i <= halfWindowSamples; i ++) {
-			double x = i * my dx / halfWindowDuration, root = 1 - x * x;
-			window [i] = root <= 0.0 ? 0.0 : NUMbessel_i0_f ((2 * NUMpi * NUMpi + 0.5) * sqrt (root));
+			const double x = i * my dx / halfWindowDuration, root = 1 - x * x;
+			window [i] = root <= 0.0 ? 0.0 : NUMbessel_i0_f ((2.0 * NUMpi * NUMpi + 0.5) * sqrt (root));
 		}
 
 		long numberOfFrames;
@@ -65,15 +65,16 @@ static autoIntensity Sound_to_Intensity_ (Sound me, double minimumPitch, double
 		try {
 			Sampled_shortTermAnalysis (me, windowDuration, timeStep, & numberOfFrames, & thyFirstTime);
 		} catch (MelderError) {
-			Melder_throw (U"The duration of the sound in an intensity analysis should be at least 6.4 divided by the minimum pitch (", minimumPitch, U" Hz), "
-				U"i.e. at least ", 6.4 / minimumPitch, U" s, instead of ", my xmax - my xmin, U" s.");
+			Melder_throw (U"The physical duration of the sound (the number of samples times the sampling period) in an intensity analysis "
+				"should be at least 6.4 divided by the minimum pitch (", minimumPitch, U" Hz), "
+				U"i.e. at least ", 6.4 / minimumPitch, U" s, instead of ", my nx * my dx, U" s.");
 		}
 		autoIntensity thee = Intensity_create (my xmin, my xmax, numberOfFrames, timeStep, thyFirstTime);
 		for (long iframe = 1; iframe <= numberOfFrames; iframe ++) {
-			double midTime = Sampled_indexToX (thee.get(), iframe);
-			long midSample = Sampled_xToNearestIndex (me, midTime);
+			const double midTime = Sampled_indexToX (thee.get(), iframe);
+			const long midSample = Sampled_xToNearestIndex (me, midTime);   // time accuracy is half a sampling period
 			long leftSample = midSample - halfWindowSamples, rightSample = midSample + halfWindowSamples;
-			double sumxw = 0.0, sumw = 0.0, intensity;
+			double sumxw = 0.0, sumw = 0.0;
 			if (leftSample < 1) leftSample = 1;
 			if (rightSample > my nx) rightSample = my nx;
 
@@ -96,9 +97,9 @@ static autoIntensity Sound_to_Intensity_ (Sound me, double minimumPitch, double
 					sumw += window [i - midSample];
 				}
 			}
-			intensity = sumxw / sumw;
-			intensity /= 4e-10;
-			thy z [1] [iframe] = intensity < 1e-30 ? -300 : 10 * log10 (intensity);
+			double intensity = sumxw / sumw;
+			intensity /= 4.0e-10;
+			thy z [1] [iframe] = intensity < 1.0e-30 ? -300.0 : 10.0 * log10 (intensity);
 		}
 		return thee;
 	} catch (MelderError) {
@@ -106,8 +107,8 @@ static autoIntensity Sound_to_Intensity_ (Sound me, double minimumPitch, double
 	}
 }
 
-autoIntensity Sound_to_Intensity (Sound me, double minimumPitch, double timeStep, int subtractMeanPressure) {
-	bool veryAccurate = false;
+autoIntensity Sound_to_Intensity (Sound me, double minimumPitch, double timeStep, bool subtractMeanPressure) {
+	const bool veryAccurate = false;
 	if (veryAccurate) {
 		autoSound up = Sound_upsample (me);   // because squaring doubles the frequency content, i.e. you get super-Nyquist components
 		return Sound_to_Intensity_ (up.get(), minimumPitch, timeStep, subtractMeanPressure);
@@ -116,7 +117,7 @@ autoIntensity Sound_to_Intensity (Sound me, double minimumPitch, double timeStep
 	}
 }
 
-autoIntensityTier Sound_to_IntensityTier (Sound me, double minimumPitch, double timeStep, int subtractMean) {
+autoIntensityTier Sound_to_IntensityTier (Sound me, double minimumPitch, double timeStep, bool subtractMean) {
 	try {
 		autoIntensity intensity = Sound_to_Intensity (me, minimumPitch, timeStep, subtractMean);
 		return Intensity_downto_IntensityTier (intensity.get());
diff --git a/fon/Sound_to_Intensity.h b/fon/Sound_to_Intensity.h
index d00bdf3..fb178db 100644
--- a/fon/Sound_to_Intensity.h
+++ b/fon/Sound_to_Intensity.h
@@ -1,6 +1,6 @@
 /* Sound_to_Intensity.h
  *
- * Copyright (C) 1992-2011,2015 Paul Boersma
+ * Copyright (C) 1992-2011,2015,2017 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
@@ -20,19 +20,19 @@
 #include "Intensity.h"
 #include "IntensityTier.h"
 
-autoIntensity Sound_to_Intensity (Sound me, double minimumPitch, double timeStep, int subtractMean);
+autoIntensity Sound_to_Intensity (Sound me, double minimumPitch, double timeStep, bool subtractMean);
 /*
 	Function:
 		smooth away the periodic part of a signal,
 		by convolving the square of the signal with a Kaiser(20.24) window;
 		and resample on original sample points.
 	Arguments:
-		'minimumPitch':
+		`minimumPitch`:
 			the minimum periodicity frequency that will be smoothed away
 			to at most 0.00001 %.
 			The Hanning/Hamming-equivalent window length will be 3.2 / 'minimumPitch'.
 			The actual window length will be twice that.
-		'timeStep':
+		`timeStep`:
 			if <= 0.0, then 0.8 / minimumPitch.
 	Performance:
 		every periodicity frequency greater than 'minimumPitch'
@@ -45,6 +45,6 @@ autoIntensity Sound_to_Intensity (Sound me, double minimumPitch, double timeStep
 		actual window duration = 64 ms;
 */
 
-autoIntensityTier Sound_to_IntensityTier (Sound me, double minimumPitch, double timeStep, int subtractMean);
+autoIntensityTier Sound_to_IntensityTier (Sound me, double minimumPitch, double timeStep, bool subtractMean);
 
 /* End of file Sound_to_Intensity.h */
diff --git a/fon/TimeSoundEditor.cpp b/fon/TimeSoundEditor.cpp
index c73d4bf..dd3f93d 100644
--- a/fon/TimeSoundEditor.cpp
+++ b/fon/TimeSoundEditor.cpp
@@ -598,13 +598,8 @@ void TimeSoundEditor_drawSound (TimeSoundEditor me, double globalMinimum, double
 			if (channelName)
 				MelderString_append (& channelLabel, U": ", channelName);
 			//
-		#if linux && ! USE_PANGO
-			MelderString_append (& channelLabel, U" ", (my d_sound.muteChannels [ichan] ? U"off": U"on"));
-		#else
-			#define UNITEXT_SPEAKER_WITH_CANCELLATION_STROKE U"\U0001F507"
-			#define UNITEXT_SPEAKER U"\U0001F508"
-			MelderString_append (& channelLabel, U" ", (my d_sound.muteChannels [ichan] ? UNITEXT_SPEAKER_WITH_CANCELLATION_STROKE: UNITEXT_SPEAKER));
-		#endif
+			MelderString_append (& channelLabel, U" ",
+				( my d_sound.muteChannels [ichan] ? UNITEXT_SPEAKER_WITH_CANCELLATION_STROKE : UNITEXT_SPEAKER ));
 			if (ichan > 8 && ichan - my d_sound.channelOffset == 1) {
 				MelderString_append (& channelLabel, U"      " UNITEXT_UPWARDS_ARROW);
 			} else if (ichan >= 8 && ichan - my d_sound.channelOffset == 8 && ichan < nchan) {
diff --git a/fon/manual_Script.cpp b/fon/manual_Script.cpp
index 3a077b5..496de93 100644
--- a/fon/manual_Script.cpp
+++ b/fon/manual_Script.cpp
@@ -1,6 +1,6 @@
 /* manual_Script.cpp
  *
- * Copyright (C) 1992-2011,2013,2014,2015 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014,2015,2016,2017 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
@@ -482,7 +482,7 @@ INTRO (U"Analogously to the formulas that you can use for creating new objects (
 	"can find in the @Modify menu when you select an object.")
 ENTRY (U"Modifying a Sound with a formula")
 NORMAL (U"Record a sound with your microphone and talk very lowly. If you don't know how to record a sound in Praat, "
-	"consult the @Intro. Once the Sound objetc is in the list, click #%Play. The result will sound very soft. "
+	"consult the @Intro. Once the Sound object is in the list, click #%Play. The result will sound very soft. "
 	"Then choose ##Formula...# from the #Modify menu and type")
 CODE (U"self * 3")
 NORMAL (U"Click OK, then click #Play again. The sound is much louder now. You have multiplied the amplitude of every sample "
@@ -702,7 +702,7 @@ TAG (U"##undefined")
 DEFINITION (U"a special value, see @undefined")
 MAN_END
 
-MAN_BEGIN (U"Formulas 4. Mathematical functions", U"ppgb", 20080318)
+MAN_BEGIN (U"Formulas 4. Mathematical functions", U"ppgb", 20170718)
 TAG (U"##abs (%x)")
 DEFINITION (U"absolute value")
 TAG (U"##round (%x)")
@@ -768,13 +768,13 @@ DEFINITION (U"the error function: 2/\\Vr%\\pi __0_\\in^%x exp(-%t^2) %dt")
 TAG (U"##erfc (%x)")
 DEFINITION (U"the complement of the error function: 1 - erf (%x)")
 TAG (U"##randomUniform (%min, %max)")
-DEFINITION (U"uniform random deviate between %min (inclusive) and %max (exclusive)")
+DEFINITION (U"uniform random real number between %min (inclusive) and %max (exclusive)")
 TAG (U"##randomInteger (%min, %max)")
-DEFINITION (U"uniform random deviate between %min and %max (inclusive)")
+DEFINITION (U"uniform random integer number between %min and %max (inclusive)")
 TAG (U"##randomGauss (%\\mu, %\\si)")
-DEFINITION (U"Gaussian random deviate with mean %\\mu and standard deviation %\\si")
+DEFINITION (U"Gaussian random real number with mean %\\mu and standard deviation %\\si")
 TAG (U"##randomPoisson (%mean)")
-DEFINITION (U"Poisson random deviate")
+DEFINITION (U"Poisson random real number")
 TAG (U"##lnGamma (%x)")
 DEFINITION (U"logarithm of the \\Ga function")
 TAG (U"##gaussP (%z)")
@@ -960,22 +960,26 @@ CODE (U"800;sqrt(2)*sin(2*pi*103*0.5)+10\\^ (-40/20)*randomGauss(0,1)")
 NORMAL (U"evaluates to 800.")
 MAN_END
 
-MAN_BEGIN (U"Formulas 7. Attributes of objects", U"ppgb", 20070225)
+MAN_BEGIN (U"Formulas 7. Attributes of objects", U"ppgb", 20170614)
 NORMAL (U"You can refer to several attributes of objects that are visible in the @@List of Objects at . "
-	"To do so, use the type and the name of the object, connected with an underscore. "
-	"Thus, $$Sound_hallo$ refers to an existing Sound object whose name is \"hallo\" "
-	"(if there is more than one such object, it refers to the one that was created last). "
-	"You can also use the unique ID instead of the name. Thus, $$Object_113$ refers to the 113th object that you created in the list.")
-NORMAL (U"To refer to an attribute, you use the period ( `.'). "
-	"Thus, $$Sound_hallo.nx$ is the number of samples of Sound_hallo, and "
-	"$$1 / Sound_hallo.dx$ is the sampling frequency of Sound_hallo.")
+	"To do so, use either the unique ID of the object, or the type and the name of the object. "
+	"Thus, $$object[113]$ refers to the object that has the number 113 in the list, "
+	"and $$object[\"Sound hallo\"]$ refers to an existing Sound object whose name is \"hallo\" "
+	"(if there is more than one such object, it refers to the one that was created last).")
+NORMAL (U"To refer to an attribute, you use the period (\".\"). "
+	"Thus, $$object[\"Sound hallo\"].nx$ is the number of samples of the Sound called %hallo, and "
+	"$$1/object[\"Sound hallo\"].dx$ is its sampling frequency.")
 ENTRY (U"Attributes in the calculator")
 NORMAL (U"Record a Sound (read the @Intro if you do not know how to do that), "
-	"name it \"mysound\" (or anything else), and type the following formula into the @calculator:")
-CODE (U"Sound_mysound.nx")
+	"and name it \"mysound\" (or anything else). An object with a name like \"3. Sound mysound\" "
+	"will appear in the list. Then type into the @calculator the formula")
+CODE (U"object[3].nx")
+NORMAL (U"or")
+CODE (U"object[\"Sound mysound\"].nx")
 NORMAL (U"After you click OK, the Info window will show the number of samples. Since you could have got this result "
-	"by simply choosing ##%%Get number of samples#% from the @Query menu, these attribute tricks are not very "
-	"useful in the calculator. We will see that they are much more useful in creation and modification formulas and in scripts.")
+	"by simply selecting the object and choosing ##%%Get number of samples#% from the @Query menu, "
+	"these attribute tricks are not very useful in the calculator. "
+	"We will see that they are much more useful in creation and modification formulas and in scripts.")
 ENTRY (U"List of possible attributes")
 NORMAL (U"The following attributes are available:")
 TAG (U"#xmin")
@@ -1023,84 +1027,79 @@ DEFINITION (U"the number of frequency bands in a Spectrogram or Cochleagram obje
 	"the number of divisions of the %y domain for a Matrix object (= %nrow).")
 TAG (U"#dy")
 DEFINITION (U"the distance between adjacent frequency bands in a Spectrogram object, in hertz; "
-	"the distance between adjacent frequency bands in a Cochleagram object, in hertz; "
+	"the distance between adjacent frequency bands in a Cochleagram object, in Bark; "
 	"the vertical distance between cells in a Matrix object.")
 ENTRY (U"Attributes in a creation formula")
 NORMAL (U"In formulas for creating a new object, you can refer to the attributes of any object, "
 	"but you will often want to refer to the attributes of the object that is just being created. You can do that in two ways.")
 NORMAL (U"The first way is to use the name of the object, as above. Choose @@Create Sound from formula...@, supply %hello for its name, "
 	"supply arbitrary values for the starting and finishing time, and type the following formula:")
-CODE (U"(x - Sound_hello.xmin) / (Sound_hello.xmax - Sound_hello.xmin)")
+CODE (U"(x - object[\"Sound hello\"].xmin) / (object[\"Sound hello\"].xmax - object[\"Sound hello\"].xmin)")
 NORMAL (U"When you edit this sound, you can see that it creates a straight line that rises from 0 to 1 within the time domain.")
 NORMAL (U"The formula above will also work if the Sound under creation is called %goodbye, and a Sound called %hello already exists; "
-	"of course, in such a case $$Sound_hello.xmax$ refers to a property of the already existing sound.")
+	"of course, in such a case $$object[\"Sound hello\"].xmax$ refers to a property of the already existing sound.")
 NORMAL (U"If a formula refers to an object under creation, there is a shorter way: you do not have to supply the name of the object at all, "
 	"so you can simply write")
 CODE (U"(x - xmin) / (xmax - xmin)")
 NORMAL (U"The attributes that you can use in this implicit way are %xmin, %xmax, %ncol, %nrow, %nx, %dx, %ny, and %dy. "
-	"To disambiguate in case there exists a script variable %xmin as well, you can write %%Self.xmin%.")
+	"To disambiguate in case there exists a script variable %xmin as well "
+	"(Praat will complain if this is the case), you can write $$Self.xmin$.")
 ENTRY (U"Attributes in a modification formula")
 NORMAL (U"In formulas for modifying an existing object, you refer to attributes in the same way as in creation formulas, "
 	"i.e., you do not have to specify the name of the object that is being modified. The formula")
 CODE (U"self * 20 \\^  (- (x - xmin) / (xmax - xmin))")
 NORMAL (U"causes the sound to decay exponentially in such a way that it has only 5 percent of its initial amplitude at the end. "
-	"If you apply this formula to multiple Sound objects at the same time, %xmax will refer to the finishing time of each Sound separately "
-	"as it is modified. To disambiguate in case there exists a script variable %xmin as well, you can write %%Self.xmin%.")
+	"If you apply this formula to multiple Sound objects at the same time, $xmax will refer to the finishing time of each Sound separately "
+	"as that Sound is modified.")
 NORMAL (U"More examples of the use of attributes are on the next page.")
 MAN_END
 
-MAN_BEGIN (U"Formulas 8. Data in objects", U"ppgb", 20140223)
+MAN_BEGIN (U"Formulas 8. Data in objects", U"ppgb", 20170614)
 NORMAL (U"With square brackets, you can get the values inside some objects.")
 ENTRY (U"Object contents in the calculator")
 NORMAL (U"The outcomes of the following examples can be checked with the @calculator.")
-TAG (U"##Matrix_hello [10, 3]")
+TAG (U"##object [%%objectName\\$  or id%, %rowNumber, %columnNumber]")
+TAG (U"$$object [\"Matrix hello\", 10, 3]")
 DEFINITION (U"gives the value in the cell at the third column of the 10th row of the Matrix called %hello.")
-TAG (U"##Sound_hello [0, 10000]")
-DEFINITION (U"gives the value (in Pa) of the 1000th sample of the Sound %hello, averaged over the channels.")
-TAG (U"##Sound_hello [1, 10000]")
-DEFINITION (U"gives the value (in Pa) of the 1000th sample of the left channel of the Sound %hello.")
-TAG (U"##Sound_hello [2, 10000]")
-DEFINITION (U"gives the value (in Pa) of the 1000th sample of the right channel of the Sound %hello.")
-TAG (U"##Sound_hello [10000]")
-DEFINITION (U"this can mean various things. In the calculator it means the same as ##Sound_hello [0, 10000]#, "
-	"but in modification formulas it can mean ##Sound_hello [row, 10000]#, where %row refers to the channel. This variation exists in order to make "
-	"older Praat scripts (from the time that Praat did not support stereo) compatible with present-day Praat versions; "
-	"because of possible confusions, the use of ##Sound_hello [10000]# is not recommended.")
-TAG (U"##TableOfReal_tokens [5, 12]")
+TAG (U"$$object [5, 10, 3]")
+DEFINITION (U"gives the value in the cell at the third column of the 10th row of the Matrix whose unique ID is 5 "
+	"(i.e. that is labelled with the number 5 in the list of objects).")
+TAG (U"$$object [\"Sound hello\", 0, 10000]")
+DEFINITION (U"gives the value (in Pa) of the 10000th sample of the Sound %hello, averaged over the channels.")
+TAG (U"$$object [23, 1, 10000]")
+DEFINITION (U"gives the value (in Pa) of the 10000th sample of the left channel of the Sound with ID 23.")
+TAG (U"$$object [23, 2, 10000]")
+DEFINITION (U"gives the value (in Pa) of the 10000th sample of the right channel of the Sound with ID 23.")
+TAG (U"$$object [\"TableOfReal tokens\", 5, 12]")
 DEFINITION (U"gives the value in the cell at the fifth row of the 12th column of the TableOfReal called %tokens.")
-TAG (U"##TableOfReal_tokens [5, \"F1\"]")
+TAG (U"$$object [\"TableOfReal tokens\", 5, \"F1\"]")
 DEFINITION (U"gives the value in the cell at the fifth row of the column labelled %F1 of the TableOfReal %tokens.")
-TAG (U"##TableOfReal_tokens [\"\\bsct\", \"F1\"]")
+TAG (U"$$object [\"TableOfReal tokens\", \"\\bsct\", \"F1\"]")
 DEFINITION (U"gives the value in the cell at the row labelled %%\\bsct% of column %F1 of the TableOfReal %tokens.")
-TAG (U"##Table_listeners [3, \"m3ae\"]")
+TAG (U"$$object [\"Table listeners\", 3, \"m3ae\"]")
 DEFINITION (U"gives the numeric value in the cell at the third row of column %m3ae of the Table %listeners.")
-TAG (U"##Table_listeners [3, 12]")
+TAG (U"$$object [\"Table listeners\", 3, 12]")
 DEFINITION (U"gives the numeric value in the cell at the third row of the 12th column of the Table %listeners.")
-TAG (U"##Table_results\\$  [3, \"response\"]")
+TAG (U"$$object\\$  [\"Table results\", 3, \"response\"]")
 DEFINITION (U"gives the string value in the cell at the third row of column %response of the Table %results.")
-TAG (U"##Table_results\\$  [3, 12]")
+TAG (U"$$object\\$  [\"Table results\", 3, 12]")
 DEFINITION (U"gives the string value in the cell at the third row of the 12th column of the Table %results.")
-TAG (U"##PitchTier_hello [8]")
+TAG (U"$$object [\"PitchTier hello\", 8]")
 DEFINITION (U"gives the pitch (in Hertz) of the 8th point in the PitchTier %hello.")
 NORMAL (U"Cells (or samples, or points) outside the objects are considered to contain zeroes.")
 ENTRY (U"Interpolation")
 NORMAL (U"The values inside some objects can be interpolated.")
-TAG (U"##Sound_hello (0.7, 0)")
+TAG (U"$$object (\"Sound hello\", 0.7, 0)")
 DEFINITION (U"gives the value (in Pa) at a time of 0.7 seconds in the Sound %hello, by linear interpolation between "
 	"the two samples that are nearest to 0.7 seconds. The channels are averaged.")
-TAG (U"##Sound_hello (0.7, 1)")
+TAG (U"$$object (\"Sound hello\", 0.7, 1)")
 DEFINITION (U"gives the interpolated value (in Pa) at a time of 0.7 seconds in the left channel of the Sound %hello.")
-TAG (U"##Sound_hello (0.7, 2)")
+TAG (U"$$object (\"Sound hello\", 0.7, 2)")
 DEFINITION (U"gives the interpolated value (in Pa) at a time of 0.7 seconds in the right channel of the Sound %hello.")
-TAG (U"##Sound_hello (0.7)")
-DEFINITION (U"this can mean various things. In the calculator it means the same as ##Sound_hello (0.7, 0)#, "
-	"but in modification formulas it can mean ##Sound_hello (0.7, row)#, where %row refers to the channel. This variation exists in order to make "
-	"older Praat scripts (from the time that Praat did not support stereo) compatible with present-day Praat versions; "
-	"because of possible confusions, the use of ##Sound_hello (0.7)# is not recommended.")
-TAG (U"##Spectrogram_hallo (0.7, 2500)")
+TAG (U"$$object (\"Spectrogram hallo\", 0.7, 2500)")
 DEFINITION (U"gives the value at a time of 0.7 seconds and at a frequency of 2500 Hz in the Spectrogram %hallo, "
 	"by linear interpolation between the four samples that are nearest to that point.")
-TAG (U"##PitchTier_hullo (0.7)")
+TAG (U"$$object (\"PitchTier hullo\", 0.7)")
 DEFINITION (U"gives the pitch (in Hertz) at a time of 0.7 seconds in the PitchTier %hullo.")
 NORMAL (U"In the interpolation, times outside the time domain of the objects are considered to contain zeroes (this does not apply to PitchTiers and the like, "
 	"which undergo @@constant extrapolation@).")
@@ -1108,30 +1107,33 @@ ENTRY (U"Object contents in a modification formula")
 NORMAL (U"Suppose you want to do the difficult way of reversing the contents of a Sound called %hello (the easy way is to choose #Reverse "
 	"from the @Modify menu). You select this sound, then choose @@Copy...@ to duplicate it to a new Sound, which you name %%hello_reverse%. "
 	"You select this new Sound and choose ##Formula...# from the @Modify menu. The formula will be")
-CODE (U"Sound_hello [ncol + 1 - col]")
+CODE (U"object [\"Sound hello\", row, ncol + 1 - col]")
 NORMAL (U"From this example, you see that the indices between [ ] may be formulas themselves, and that you can use implicit attributes like %ncol "
-	"and position references like %col. An alternative formula is")
-CODE (U"Sound_hello (xmax - x)")
-NORMAL (U"at least if %xmin is zero. The advantage of the second method is that is also works correctly if the two sounds have different sampling frequencies; "
-	"the disadvantage is that it may do some interpolation between the samples, which deteriorates the sound quality.")
+	"and position references like %col (also %row, which here means that the reversal is performed for each channel). "
+	"An alternative formula is")
+CODE (U"object (\"Sound hello\", xmax - x, y)")
+NORMAL (U"at least if %xmin is zero. The advantage of the second method is that it also works correctly if the two sounds have different sampling frequencies; "
+	"the disadvantage is that it may do some interpolation between the samples, which deteriorates the sound quality "
+	"(the use of %y here means that the reversal is done for all %y values, i.e. all channels).")
 ENTRY (U"Object contents in a script")
 NORMAL (U"In scripts, the indices between [ ] and the values between ( ) may be formulas themselves and contain variables. "
-	"The following script computes the sum of all the cells along the diagonal of a Matrix named %hello.")
+	"The following script computes the sum of all the cells along the diagonal of a Matrix.")
+CODE (U"matrix = Create simple matrix: 10, 10, \"x*y\"")
 CODE (U"sumDiagonal = 0")
-CODE (U"for i to Matrix_hello.ncol")
-	CODE1 (U"sumDiagonal += Matrix_hello [i, i]")
+CODE (U"for i to object[matrix].ncol")
+	CODE1 (U"sumDiagonal += object [matrix, i, i]")
 CODE (U"endfor")
-CODE (U"writeInfoLine: \"The sum of cells along the diagonal is \", sumDiagonal, \".\"")
+CODE (U"writeInfoLine: \"The sum of the cells along the diagonal is \", sumDiagonal, \".\"")
 NORMAL (U"This example could have been written completely with commands from the dynamic menu:")
-CODE (U"select Matrix hello")
+CODE (U"matrix = Create simple matrix: 10, 10, \"x*y\"")
 CODE (U"sumDiagonal = 0")
 CODE (U"ncol = Get number of columns")
 CODE (U"for i to ncol")
 	CODE1 (U"value = Get value in cell: i, i")
 	CODE1 (U"sumDiagonal += value")
 CODE (U"endfor")
-CODE (U"writeInfoLine: \"The sum of cells along the diagonal is \", sumDiagonal, \".\"")
-NORMAL (U"The first version, which accesses the contents directly, is not only three lines shorter, but also three times faster.")
+CODE (U"writeInfoLine: \"The sum of the cells along the diagonal is \", sumDiagonal, \".\"")
+NORMAL (U"The first version, which accesses the contents directly, is not only two lines shorter, but also three times faster.")
 MAN_END
 
 MAN_BEGIN (U"Hidden commands", U"ppgb", 20110129)
@@ -1355,7 +1357,7 @@ NORMAL (U"On Linux the file is called #prefs5, "
 	"for instance ##/home/miep/.praat-dir/prefs5#.")
 MAN_END
 
-MAN_BEGIN (U"Scripting", U"ppgb", 20141012)
+MAN_BEGIN (U"Scripting", U"ppgb", 20170718)
 INTRO (U"This is one of the tutorials of the Praat program. It assumes you are familiar with the @Intro.")
 NORMAL (U"A %script is a text that consists of menu commands and action commands. "
 	"If you %run the script (perhaps from a @ScriptEditor), "
@@ -1382,8 +1384,9 @@ LIST_ITEM1 (U"@@Scripting 5.3. Jumps@ (if, then, elsif, else, endif)")
 LIST_ITEM1 (U"@@Scripting 5.4. Loops@ (for/endfor, while/endwhile, repeat/until)")
 LIST_ITEM1 (U"@@Scripting 5.5. Procedures@ (\\@ , procedure)")
 LIST_ITEM1 (U"@@Scripting 5.6. Arrays and dictionaries")
-LIST_ITEM1 (U"@@Scripting 5.7. Including other scripts")
-LIST_ITEM1 (U"@@Scripting 5.8. Quitting@ (exitScript)")
+LIST_ITEM1 (U"@@Scripting 5.7. Vectors and matrices")
+LIST_ITEM1 (U"@@Scripting 5.8. Including other scripts")
+LIST_ITEM1 (U"@@Scripting 5.9. Quitting@ (exitScript)")
 LIST_ITEM (U"@@Scripting 6. Communication outside the script")
 LIST_ITEM1 (U"@@Scripting 6.1. Arguments to the script@ (form/endform, runScript)")
 LIST_ITEM1 (U"@@Scripting 6.2. Writing to the Info window@ (writeInfoLine, appendInfoLine, appendInfo, tab\\$ )")
@@ -2216,7 +2219,7 @@ CODE (U"#for i from 1 to n")
 CODE (U"#endfor")
 MAN_END
 
-MAN_BEGIN (U"Scripting 5. Language elements reference", U"ppgb", 20160405)
+MAN_BEGIN (U"Scripting 5. Language elements reference", U"ppgb", 20170718)
 NORMAL (U"In a Praat script, you can use variables, expressions, and functions, of numeric as well as string type, "
 	"and most of the control structures known from other procedural computer languages. "
 	"The way the distinction between numbers and strings is made, may remind you of the programming language Basic.")
@@ -2226,8 +2229,9 @@ LIST_ITEM (U"@@Scripting 5.3. Jumps@ (if, then, elsif, else, endif)")
 LIST_ITEM (U"@@Scripting 5.4. Loops@ (for/endfor, while/endwhile, repeat/until)")
 LIST_ITEM (U"@@Scripting 5.5. Procedures@ (\\@ , procedure)")
 LIST_ITEM (U"@@Scripting 5.6. Arrays and dictionaries@")
-LIST_ITEM (U"@@Scripting 5.7. Including other scripts@")
-LIST_ITEM (U"@@Scripting 5.8. Quitting@ (exit)")
+LIST_ITEM (U"@@Scripting 5.7. Vectors and matrices")
+LIST_ITEM (U"@@Scripting 5.8. Including other scripts@")
+LIST_ITEM (U"@@Scripting 5.9. Quitting@ (exit)")
 MAN_END
 
 MAN_BEGIN (U"Scripting 5.1. Variables", U"ppgb", 20140111)
@@ -2649,7 +2653,7 @@ CODE (U"#endproc")
 NORMAL (U"However, this uses variable substitution, a trick better avoided.")
 MAN_END
 
-MAN_BEGIN (U"Scripting 5.6. Arrays and dictionaries", U"ppgb", 20160405)
+MAN_BEGIN (U"Scripting 5.6. Arrays and dictionaries", U"ppgb", 20170718)
 NORMAL (U"You can use arrays of numeric and string variables:")
 CODE (U"#for i #from 1 #to 5")
 	CODE1 (U"square [i] = i * i")
@@ -2662,15 +2666,109 @@ CODE (U"#writeInfoLine: \"Some squares:\"")
 CODE (U"#for i #from 1 #to 5")
 	CODE1 (U"#appendInfoLine: \"The square of \", i, \" is \", square [i]")
 CODE (U"#endfor")
-NORMAL (U"You can use any number of variables in a script, but you can also use Matrix or Sound objects for arrays.")
 NORMAL (U"In the examples above, the %index into the array was always a number. "
 	" A %hash or %dictionary is an array variable where the index is a string:")
 CODE (U"age [\"John\"] = 36")
 CODE (U"age [\"Babs\"] = 39")
 CODE (U"#writeInfoLine: \"John is \", age [\"John\"], \" years old.\"")
+ENTRY (U"See also")
+NORMAL (U"You can use any number of array and dictionary variables in a script, "
+	"but for many applications, namely whenever it were useful to look at a numeric array as a single object, "
+	"it may be better to use vectors and matrices (see @@Scripting 5.7. Vectors and matrices@) "
+	"or to use Matrix or Sound objects.")
+MAN_END
+
+MAN_BEGIN (U"Scripting 5.7. Vectors and matrices", U"ppgb", 20170722)
+ENTRY (U"1. What is a vector?")
+NORMAL (U"A ##numeric vector# is an array of numbers, regarded as a single object. "
+	"For instance, the squares of the first five integers can be collected in the vector { 1, 4, 9, 16, 25 }. "
+	"In a Praat script, you can put a vector into a variable whose name ends in a number sign (\"\\# \"):")
+CODE (U"squares\\#  = { 1, 4, 9, 16, 25 }")
+NORMAL (U"After this, the variable %%squares\\# % contains the value { 1, 4, 9, 16, 25 }. "
+	"We say that the vector %%squares\\# % has five %dimensions, i.e. it contains five numbers.")
+NORMAL (U"Whereas in @@Scripting 3.2. Numeric variables@ we talked about a numeric variable as being analogous to a house "
+	"where somebody (the numeric %value) could live, a numeric vector with five dimensions "
+	"can be seen as a %street that contains five houses, which are numbered with the indexes 1, 2, 3, 4 and 5, "
+	"each house containing a numeric value. Thus, the street %%squares\\# % contains the following five houses: "
+	"%%squares\\# % [1], %%squares\\# % [2], %%squares\\# % [3], %%squares\\# % [4] and %%squares\\# % [5]. "
+	"Their values (the numbers that currently live in these houses) are 1, 4, 9, 16 and 25, respectively.")
+NORMAL (U"To list the five values with a loop, you could do:")
+CODE (U"#writeInfoLine: \"Some squares:\"")
+CODE (U"#for i #from 1 #to 5")
+	CODE1 (U"#appendInfoLine: \"The square of \", i, \" is \", squares\\#  [i]")
+CODE (U"#endfor")
+NORMAL (U"Instead of the above procedure to get the vector %%squares\\# %, with a pre-computed list of five squares, "
+	"you could compute the five values with a formula, as in the example of @@Scripting 5.6. Arrays and dictionaries at . "
+	"However, in order to put a value into an element of the vector, you have to create the vector first "
+	"(i.e., you have to build the whole street before you can put something in a house), "
+	"so we start by creating a vector with five zeroes in it:")
+CODE (U"squares\\#  = zero\\#  (5)")
+NORMAL (U"After this, %%squares\\# % is the vector { 0, 0, 0, 0, 0 }, i.e., the value of each element is zero. "
+	"Now that the vector (street) exists, we can put values into (populate) the five elements (houses):")
+CODE (U"#for i #from 1 #to 5")
+	CODE1 (U"squares\\#  [i] = i * i")
+CODE (U"#endfor")
+NORMAL (U"After this, the variable $$squares\\# $ has the value { 1, 4, 9, 16, 25 }, as before, "
+	"but now we had the computer compute the squares.")
+ENTRY (U"2. Creating a vector")
+NORMAL (U"You can create a vector in many ways. The first way we saw was with a ##vector literal#, "
+	"i.e. a series of numbers (or numeric formulas) between braces:")
+CODE (U"lengths\\#  = { 1.83, 1.795, 1.76 }")
+NORMAL (U"The second way we saw was to create a series of #zeroes. To create a vector consisting of 10,000 zeroes, you do")
+CODE (U"zero\\#  (10000)")
+NORMAL (U"Another important type of vector is a series of random numbers. "
+	"To create a vector consisting of 10,000 values drawn from a ##Gaussian distribution# "
+	"with true mean 0.0 and true standard deviation 1.0, you could do")
+CODE (U"noise\\#  = randomGauss\\#  (10000, 0.0, 1.0)")
+NORMAL (U"To create a vector consisting of 10,000 values drawn from a ##uniform distribution of real numbers# "
+	"with true minimum 0.0 and true maximum 1.0, you use")
+CODE (U"randomUniform\\#  (10000, 0.0, 1.0)")
+NORMAL (U"To create a vector consisting of 10,000 values drawn from a ##uniform distribution of integer numbers# "
+	"with true minimum 1 and true maximum 10, you use")
+CODE (U"randomInteger\\#  (10000, 1, 10)")
+NORMAL (U"Vectors can also be created by some menu commands. For instance, to get vectors representing "
+	"the times and pitch frequencies of the frames in a Pitch object, you can do")
+CODE (U"selectObject: myPitch")
+CODE (U"times\\#  = Get times of frames")
+CODE (U"pitches\\#  = Get values in frames")
+ENTRY (U"3. Turning a vector into a number")
+NORMAL (U"For the vector defined above, you can compute the #sum of the five values as")
+CODE (U"sum (squares\\# )")
+NORMAL (U"which gives 55. You compute the #average of the five values as")
+CODE (U"mean (squares\\# )")
+NORMAL (U"which gives 11. You compute the ##standard deviation# of the values as ")
+CODE (U"stdev (squares\\# )")
+NORMAL (U"which gives 9.669539802906858 (the standard deviation is undefined for vectors with fewer than 2 elements). "
+	"The ##center of gravity# of the distribution defined by regarding "
+	"the five values as relative frequencies as a function of the index from 1 to 5 is computed by")
+CODE (U"center (squares\\# )")
+NORMAL (U"which gives 4.090909090909091 (for vector with five elements, the result will always be "
+	"a number between 1.0 and 5.0). You compute the ##inner product# of two equally long vectors as follows:")
+CODE (U"other\\#  = { 2, 1.5, 1, 0.5, 0 }")
+CODE (U"result\\#  = inner (square\\# , other\\# )")
+NORMAL (U"which gives 1*2 + 4*1.5 + 9*1 + 16*0.5 + 25*0 = 25. "
+	"The formula for this is \\su__%i=1_^5 square[i] * other[i], so that an alternative piece of 1code could be")
+CODE (U"result\\#  = sumOver (i to 5, square\\#  [i] * other\\#  [i])")
+ENTRY (U"4. Converting vectors to vectors")
+CODE (U"a\\#  = squares\\#  + 5   ; adding a number to each element of a vector")
+NORMAL (U"causes a\\#  to become the vector { 6, 9, 14, 21, 30 }.")
+CODE (U"b\\#  = a\\#  + { 3.14, 2.72, 3.16, -1, 7.5 }   ; adding two vectors of the same length")
+NORMAL (U"causes b\\#  to become the vector { 9.14, 16.72, 17.16, 20, 37.5 }.")
+CODE (U"c\\#  = b\\#  / 2   ; dividing each element of a vector")
+NORMAL (U"causes c\\#  to become the vector { 4.57, 8.36, 8.58, 10, 18.75 }.")
+CODE (U"d\\#  = b\\#  * c\\#    ; elementwise multiplication")
+NORMAL (U"causes d\\#  to become the vector { xx, 8.36, 8.58, 10, 18.75 }.")
+NORMAL (U"A vector can also be given to a ##menu command# that returns another vector. "
+	"For instance, to get a vector representing the pitch frequencies at 0.01-second intervals in a Pitch object, "
+	"you can do")
+CODE (U"selectObject: myPitch")
+CODE (U"tmin = Get start time")
+CODE (U"tmax = Get end time")
+CODE (U"times\\#  = linear\\#  (tmin, tmax, 0.01, xx)")
+CODE (U"pitches\\#  = Get values at times: times\\# , \"hertz\", \"linear\"")
 MAN_END
 
-MAN_BEGIN (U"Scripting 5.7. Including other scripts", U"ppgb", 20140111)
+MAN_BEGIN (U"Scripting 5.8. Including other scripts", U"ppgb", 20170718)
 INTRO (U"You can include other scripts within your script:")
 CODE (U"a = 5")
 CODE (U"include square.praat")
@@ -2692,7 +2790,7 @@ NORMAL (U"You can \"nest\" include files, i.e., included scripts can include oth
 NORMAL (U"The #include statement can only be at the start of a line: you cannot put any spaces in front of it.")
 MAN_END
 
-MAN_BEGIN (U"Scripting 5.8. Quitting", U"ppgb", 20140124)
+MAN_BEGIN (U"Scripting 5.9. Quitting", U"ppgb", 20170718)
 NORMAL (U"Usually, the execution of your script ends when the interpreter has executed the last line "
 	"that is not within a procedure definition. However, you can also explicitly stop the script:")
 TAG (U"#exitScript ( )")
@@ -3160,9 +3258,9 @@ NORMAL (U"In this example, $$fonsg19.hum.uva.nl$ is the computer on which MovieE
 NORMAL (U"The number 6667 is the port number on which MovieEdit is listening. Other programs will use different port numbers.")
 MAN_END
 
-MAN_BEGIN (U"Scripting 6.8. Messages to the user", U"ppgb", 201401024)
+MAN_BEGIN (U"Scripting 6.8. Messages to the user", U"ppgb", 20170718)
 NORMAL (U"If the user makes a mistake (e.g. types conflicting settings into your form window), "
-	"you can use the #exitScript function (@@Scripting 5.8. Quitting|\\SS5.8@) "
+	"you can use the #exitScript function (@@Scripting 5.9. Quitting|\\SS5.8@) "
 	"to stop the execution of the script with an error message:")
 CODE (U"form My analysis")
 	CODE1 (U"real Starting_time_(s) 0.0")
@@ -3240,7 +3338,7 @@ NORMAL (U"Praat will start up, opening the script $$my script.praat$$ in a scrip
 NORMAL (U"On the Mac, you do")
 CODE (U"/Applications/Praat.app/Contents/MacOS/Praat --open \"my script.praat\"")
 NORMAL (U"and on Linux")
-CODE (U"/usr/bin/praat data/hello.wav --open \"my script.praat\"")
+CODE (U"/usr/bin/praat --open \"my script.praat\"")
 NORMAL (U"Note that on all three platforms, you have to supply quotes around the file name "
 	"if that file name contains one or more spaces, as here between $$my$ and $$script$ "
 	"or above between $$Program$ and $$Files$. This is because the script languages of "
@@ -3253,7 +3351,7 @@ CODE (U"\"C:\\bsProgram Files\\bsPraat.exe\" --run \"my script.praat\"")
 NORMAL (U"Praat will execute the script $$my script.praat$$ without showing Praat's GUI, "
 	"i.e. without showing its usual two windows. "
 	"In fact, any output that would normally go to the Info window, "
-	"will now go directly to the Console window in which you typed te command. "
+	"will now go directly to the Console window in which you typed the command. "
 	"If Praat was already running when you typed the command, "
 	"its windows will not be affected. In fact, the GUI-instantiation of Praat and the Console-instantiation "
 	"can run simultaneously without them noticing each other's existence; "
@@ -3261,7 +3359,7 @@ NORMAL (U"Praat will execute the script $$my script.praat$$ without showing Praa
 NORMAL (U"On the Mac, you type")
 CODE (U"/Applications/Praat.app/Contents/MacOS/Praat --run \"my script.praat\"")
 NORMAL (U"and on Linux")
-CODE (U"/usr/bin/praat data/hello.wav --run \"my script.praat\"")
+CODE (U"/usr/bin/praat --run \"my script.praat\"")
 NORMAL (U"What happens on all platforms is that the Console or Terminal starts up Praat, "
 	"then Praat executes the script, and then Praat closes itself.")
 
@@ -4083,7 +4181,7 @@ CODE (U"endwhile")
 NORMAL (U"The first sound will stop playing soon after the user clicks for the second time.")
 ENTRY (U"Animation")
 NORMAL (U"In the above examples, things will often get drawn to the screen with some delay, "
-	"i.e., you may not see the erasures and paintings happening. This is because several operating systems "
+	"i.e., you may not see the erasures and paintings happen. This is because several operating systems "
 	"use %buffering of graphics. These systems will draw the graphics only just before getting user input. "
 	"This means that #demoWaitForInput is the place where your drawings will typically be painted on the screen. "
 	"If you want painting to happen earlier (e.g. in animations), you can use")
diff --git a/fon/manual_glossary.cpp b/fon/manual_glossary.cpp
index 35e92fe..85ecf14 100644
--- a/fon/manual_glossary.cpp
+++ b/fon/manual_glossary.cpp
@@ -164,7 +164,7 @@ NORMAL (U"Between 0.10 and 0.20 seconds, the pitch rises linearly from 170 to 18
 	"and between 0.20 and 0.45 seconds it falls linearly from 180 to 110 Hz. "
 	"This is %%linear interpolation%: at all times between two adjacent points, "
 	"the pitch values follow the straight line that connects the two points.")
-NORMAL (U"(Before 0.10 seconds and after 0.45 seconds there is @@constant extrapolation at .")
+NORMAL (U"(Before 0.10 seconds and after 0.45 seconds there is @@constant extrapolation at .)")
 ENTRY (U"Linear interpolation in Praat")
 NORMAL (U"Praat uses linear interpolation in all tiers and grids with values at time points "
 	"(@PitchTier, @IntensityTier, @DurationTier, @AmplitudeTier, @FormantGrid).")
diff --git a/fon/manual_pitch.cpp b/fon/manual_pitch.cpp
index c66e219..f904ff2 100644
--- a/fon/manual_pitch.cpp
+++ b/fon/manual_pitch.cpp
@@ -1,6 +1,6 @@
 /* manual_pitch.cpp
  *
- * Copyright (C) 1992-2010,2015 Paul Boersma
+ * Copyright (C) 1992-2010,2015,2017 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
@@ -472,7 +472,7 @@ ENTRY (U"Behaviour")
 NORMAL (U"The times of all the pitch points are trivially copied, and so is the time domain. The pitch information is lost.")
 MAN_END
 
-MAN_BEGIN (U"PitchTier: Get mean (curve)...", U"ppgb", 20010821)
+MAN_BEGIN (U"PitchTier: Get mean (curve)...", U"ppgb", 20170618)
 INTRO (U"A @query to the selected @PitchTier object.")
 ENTRY (U"Return value")
 NORMAL (U"the mean of the curve within a specified time window.")
@@ -489,7 +489,7 @@ NORMAL (U"The mean is the sum of these values divided by %toTime \\-- %fromTime.
 NORMAL (U"For a PitchTier that was created from a @Pitch object, this command gives the same result as "
 	"##Get mean....# for the original Pitch object (but remember that the median, "
 	"as available for Pitch objects, is more robust).")
-NORMAL (U"To get the mean in the entire curve, i.e. weighted by the durations of the line pieces, "
+NORMAL (U"To get the mean of the pitch points, i.e. not weighted by the durations of the line pieces, "
 	"Use @@PitchTier: Get mean (points)...@ instead.")
 MAN_END
 
diff --git a/fon/manual_programming.cpp b/fon/manual_programming.cpp
index 89c8ab2..9a2b348 100644
--- a/fon/manual_programming.cpp
+++ b/fon/manual_programming.cpp
@@ -1,6 +1,6 @@
 /* manual_programming.cpp
  *
- * Copyright (C) 1992-2010,2015 Paul Boersma
+ * Copyright (C) 1992-2010,2015,2017 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
@@ -34,13 +34,13 @@ NORMAL (U"• Many programs read and/or write Praat TextGrid files. If you want
 	"consult @@TextGrid file formats at .")
 MAN_END
 
-MAN_BEGIN (U"TextGrid file formats", U"ppgb", 20151107)
+MAN_BEGIN (U"TextGrid file formats", U"ppgb", 20170527)
 INTRO (U"This page describes the syntax and semantics of TextGrid files that Praat can read and/or write.")
 ENTRY (U"1. The full text format of a minimal TextGrid")
 NORMAL (U"If you record a Sound with a druation of 2.3 seconds, and then do ##To TextGrid...#, "
 	"you are asked to provide tier names and to say which of these tiers are point tiers. "
 	"If you click OK without changing the settings from their standard values, "
-	" you obtain a TextGrid with two interval tiers, called %Mary and %John, and one point tier called %bell. "
+	"you obtain a TextGrid with two interval tiers, called %Mary and %John, and one point tier called %bell. "
 	"When you save this TextGrid to disk by choosing @@Save as text file...@ from the #New menu, "
 	"the resulting text file, when opened in a text editor, will look as follows:")
 CODE (U"File type = \"ooTextFile\"")
@@ -288,13 +288,13 @@ NORMAL (U"A weaker invariant is that the starting time of the first interval on
 	"to honour this weak invariant.")
 NORMAL (U"For a point tier, a strong invariant is that the time of each point (except the first) has to be greater than the time "
 	"of the previous point. Praat maintains this invariant for instance by refusing to insert a point at a time of an existing "
-	"point. TextGrid files that violate this invariant may or may not be read by Praat, and may cause strange behviour in Praat "
+	"point. TextGrid files that violate this invariant may or may not be read by Praat, and may cause strange behaviour in Praat "
 	"if they are read.")
 NORMAL (U"A further weak invariant is that the starting and end times of each tier equal the starting and end times of the whole "
 	"TextGrid. This can be violated by combining multiple TextGrids into one, but other programs are advised to create TextGrids "
 	"that honour this invariant, because TextGrids that violate it may look strange to the user.")
 ENTRY (U"7. Text encoding")
-NORMAL (U"Existing TextGrid text files come in various encoding. When creating a parser for TextGrid text files, "
+NORMAL (U"Existing TextGrid text files come in various encodings. When creating a parser for TextGrid text files, "
 	"you should be prepared for reading it in UTF-8 encoding (without Byte Order Mark), or in UTF-16 encoding "
 	"(either Big-Endian or Little-Endian, with Byte Order Mark). Pre-Unicode TextGrid text files may have a Latin-1 encoding "
 	"if they were created on Windows or Linux, or a MacRoman encoding if they were created on a Mac, "
@@ -302,7 +302,8 @@ NORMAL (U"Existing TextGrid text files come in various encoding. When creating a
 	"(line separators, as described below, may help).")
 NORMAL (U"When writing a TextGrid text file, you can use UTF-8 encoding (without Byte Order Mark), or UTF-16 encoding "
 	"(either Big-Endian or Little-Endian, with Byte Order Mark). "
-	"Please never write a limited encoding such as Latin-1 or MacRoman.")
+	"Please never write a limited encoding such as Latin-1 or MacRoman, which do not support international characters "
+	"or phonetic characters as in $$\"ʔaɦɔj\"$.")
 NORMAL (U"The lines in the file are typically separated by a newline symbol (Linux or modern Mac), "
 	"or by a Return symbol (old Mac), or by a Return symbol followed by a newline symbol (Windows). "
 	"When reading a TextGrid text file, you should be prepared for each of these line separators. "
@@ -341,7 +342,7 @@ CODE (U"item []:")
 		CODE2 (U"intervals [1]:")
 			CODE3 (U"xmin = 0")
 			CODE3 (U"xmax = 0.7")
-			CODE3 (U"text = \"r\\bsT^i\\bs:fk\\bsefj\"")
+			CODE3 (U"text = \"r\\bsT\\^ i\\bs:fk\\bsefj\"")
 		CODE2 (U"intervals [2]:")
 			CODE3 (U"xmin = 0.7")
 			CODE3 (U"xmax = 1.6")
diff --git a/fon/manual_tutorials.cpp b/fon/manual_tutorials.cpp
index fb43c0b..6039b92 100644
--- a/fon/manual_tutorials.cpp
+++ b/fon/manual_tutorials.cpp
@@ -22,10 +22,16 @@
 void manual_tutorials_init (ManPages me);
 void manual_tutorials_init (ManPages me) {
 
-MAN_BEGIN (U"What's new?", U"ppgb", 20170524)
+MAN_BEGIN (U"What's new?", U"ppgb", 20170722)
 INTRO (U"Latest changes in Praat.")
 //LIST_ITEM (U"• Manual page about @@drawing a vowel triangle at .")
 
+NORMAL (U"##6.0.30# (22 July 2017)")
+LIST_ITEM (U"• Removed a bug that caused an incorrect title for a PitchTier or PointProcess window.")
+LIST_ITEM (U"• Removed a bug that caused Praat to crash when doing a linear regression on a Table with no rows.")
+LIST_ITEM (U"• Scripting: $$object[]$, @@Scripting 5.7. Vectors and matrices|vectors at .")
+LIST_ITEM (U"• Graphics: better text drawing details.")
+LIST_ITEM (U"• Linux: possibility to compile Praat without a GUI but with graphics file output.")
 NORMAL (U"##6.0.29# (24 May 2017)")
 LIST_ITEM (U"• Sound window: channel muting.")
 LIST_ITEM (U"• Linux: support for Chinese, Japanese, Korean, Indic, Arabic and Hebrew characters in TextGrids and elsewhere.")
@@ -1477,7 +1483,7 @@ ENTRY (U"Praat 4.1, 5 June 2003")
 		"or Table_tokens [i, \"F1\"].")
 	LIST_ITEM (U"• Assignment by modification, as with += -= *= /=.")
 	LIST_ITEM (U"• New functions: date\\$ (), extractNumber, extractWord\\$ , extractLine\\$ . See @@Formulas 5. String functions at .")
-	LIST_ITEM (U"• @@Scripting 5.7. Including other scripts at .")
+	LIST_ITEM (U"• @@Scripting 5.8. Including other scripts at .")
 	LIST_ITEM (U"• String formulas in the calculator.")
 	LIST_ITEM (U"• Stopped support of things that had been undocumented for the last four years: "
 		"#let, #getnumber, #getstring, #ARGS, #copy, #proc, variables with capitals, and strings in numeric variables; "
diff --git a/fon/praat_Fon.cpp b/fon/praat_Fon.cpp
index 88ee6cd..467dbb4 100644
--- a/fon/praat_Fon.cpp
+++ b/fon/praat_Fon.cpp
@@ -1,6 +1,6 @@
 /* praat_Fon.cpp
  *
- * Copyright (C) 1992-2012,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014,2015,2016,2017 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
@@ -172,13 +172,12 @@ END }
 
 DIRECT (WINDOW_Corpus_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot edit a Corpus from batch.");
-	LOOP {
-		iam (Corpus);
+	FIND_ONE_WITH_IOBJECT (Corpus)
 		autoTableEditor editor = TableEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: - DISTRIBUTIONS
 
@@ -1281,14 +1280,13 @@ static void cb_ManipulationEditor_publication (Editor /* editor */, autoDaata pu
 }
 DIRECT (WINDOW_Manipulation_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Manipulation from batch.");
-	LOOP {
-		iam (Manipulation);
+	FIND_ONE_WITH_IOBJECT (Manipulation)
 		autoManipulationEditor editor = ManipulationEditor_create (ID_AND_FULL_NAME, me);
 		Editor_setPublicationCallback (editor.get(), cb_ManipulationEditor_publication);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 DIRECT (NEW_Manipulation_extractDurationTier) {
 	CONVERT_EACH (Manipulation)
@@ -1552,13 +1550,12 @@ DO
 
 DIRECT (WINDOW_Pitch_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Pitch from batch.");
-	LOOP {
-		iam (Pitch);
+	FIND_ONE_WITH_IOBJECT (Pitch)
 		autoPitchEditor editor = PitchEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 FORM (MODIFY_Pitch_formula, U"Pitch: Formula", U"Formula...") {
 	LABEL (U"", U"x = time; col = frame; row = candidate (1 = current path); frequency (time, candidate) :=")
@@ -2224,13 +2221,12 @@ DO
 
 DIRECT (WINDOW_Spectrogram_view) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Spectrogram from batch.");
-	LOOP {
-		iam (Spectrogram);
+	FIND_ONE_WITH_IOBJECT (Spectrogram)
 		autoSpectrogramEditor editor = SpectrogramEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: - SPECTRUM
 
@@ -2244,13 +2240,12 @@ DIRECT (HELP_Spectrum_help) {
 
 DIRECT (WINDOW_Spectrum_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Spectrum from batch.");
-	LOOP {
-		iam (Spectrum);
+	FIND_ONE_WITH_IOBJECT (Spectrum)
 		autoSpectrumEditor editor = SpectrumEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Draw
 
@@ -2651,13 +2646,12 @@ DIRECT (HELP_Strings_help) {
 
 DIRECT (WINDOW_Strings_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Strings from batch.");
-	LOOP {
-		iam (Strings);
+	FIND_ONE_WITH_IOBJECT (Strings)
 		autoStringsEditor editor = StringsEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Query
 
diff --git a/fon/praat_Matrix.cpp b/fon/praat_Matrix.cpp
index d3e0d97..6b2ca3c 100644
--- a/fon/praat_Matrix.cpp
+++ b/fon/praat_Matrix.cpp
@@ -1,6 +1,6 @@
 /* praat_Matrix.cpp
  *
- * Copyright (C) 1992-2012,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014,2015,2016,2017 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
@@ -781,13 +781,12 @@ DO
 
 DIRECT (WINDOW_Movie_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Movie from batch.");
-	LOOP {
-		iam (Movie);
+	FIND_ONE_WITH_IOBJECT (Movie)
 		autoMovieWindow editor = MovieWindow_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: file recognizers
 
diff --git a/fon/praat_Sound.cpp b/fon/praat_Sound.cpp
index 4ea8c28..db765ae 100644
--- a/fon/praat_Sound.cpp
+++ b/fon/praat_Sound.cpp
@@ -1,6 +1,6 @@
 /* praat_Sound_init.cpp
  *
- * Copyright (C) 1992-2012,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2014,2015,2016,2017 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
@@ -158,13 +158,12 @@ DO
 
 DIRECT (WINDOW_LongSound_view) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a LongSound from batch.");
-	LOOP {
-		iam (LongSound);
+	FIND_ONE_WITH_IOBJECT (LongSound)
 		autoSoundEditor editor = SoundEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 FORM_SAVE (SAVE_LongSound_saveAsAifcFile, U"Save as AIFC file", nullptr, U"aifc") {
 	SAVE_TYPED_LIST (Sampled, SoundAndLongSoundList)
@@ -659,12 +658,10 @@ static void cb_SoundEditor_publication (Editor /* me */, autoDaata publication)
 		praat_updateSelection ();
 		if (isaSpectrum) {
 			int IOBJECT;
-			LOOP {
-				iam (Spectrum);
-				autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
-				praat_installEditor (editor2.get(), IOBJECT);
-				editor2.releaseToUser();
-			}
+			FIND_ONE_WITH_IOBJECT (Spectrum)
+			autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
+			praat_installEditor (editor2.get(), IOBJECT);
+			editor2.releaseToUser();
 		}
 	} catch (MelderError) {
 		Melder_flushError ();
@@ -672,14 +669,13 @@ static void cb_SoundEditor_publication (Editor /* me */, autoDaata publication)
 }
 DIRECT (WINDOW_Sound_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a Sound from batch.");
-	LOOP {
-		iam (Sound);
+	FIND_ONE_WITH_IOBJECT (Sound)
 		autoSoundEditor editor = SoundEditor_create (ID_AND_FULL_NAME, me);
 		Editor_setPublicationCallback (editor.get(), cb_SoundEditor_publication);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 DIRECT (NEWMANY_Sound_extractAllChannels) {
 	LOOP {
diff --git a/fon/praat_TextGrid_init.cpp b/fon/praat_TextGrid_init.cpp
index a59f1b9..d612d70 100644
--- a/fon/praat_TextGrid_init.cpp
+++ b/fon/praat_TextGrid_init.cpp
@@ -691,12 +691,10 @@ static void cb_TextGridEditor_publication (Editor /* editor */, autoDaata public
 		praat_updateSelection ();
 		if (isaSpectralSlice) {
 			int IOBJECT;
-			LOOP {
-				iam (Spectrum);
-				autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
-				praat_installEditor (editor2.get(), IOBJECT);
-				editor2.releaseToUser();
-			}
+			FIND_ONE_WITH_IOBJECT (Spectrum)
+			autoSpectrumEditor editor2 = SpectrumEditor_create (ID_AND_FULL_NAME, me);
+			praat_installEditor (editor2.get(), IOBJECT);
+			editor2.releaseToUser();
 		}
 	} catch (MelderError) {
 		Melder_flushError ();
@@ -704,36 +702,26 @@ static void cb_TextGridEditor_publication (Editor /* editor */, autoDaata public
 }
 DIRECT (WINDOW_TextGrid_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a TextGrid from batch.");
-	Sound sound = nullptr;
-	LOOP {
-		if (CLASS == classSound) sound = (Sound) OBJECT;   // may stay null
-	}
-	LOOP if (CLASS == classTextGrid) {
-		iam (TextGrid);
-		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, sound, true, nullptr, nullptr);
+	FIND_TWO_WITH_IOBJECT (TextGrid, Sound)   // Sound may be NULL
+		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, you, true, nullptr, nullptr);
 		Editor_setPublicationCallback (editor.get(), cb_TextGridEditor_publication);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 FORM (WINDOW_TextGrid_viewAndEditWithCallback, U"TextGrid: View & Edit with callback", nullptr) {
 	SENTENCE4 (callbackText, U"Callback text", U"r1")
 	OK
 DO
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a TextGrid from batch.");
-	Sound sound = nullptr;
-	LOOP {
-		if (CLASS == classSound) sound = (Sound) OBJECT;   // may stay null
-	}
-	LOOP if (CLASS == classTextGrid) {
-		iam (TextGrid);
-		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, sound, true, nullptr, Melder_peek32to8 (callbackText));
+	FIND_TWO_WITH_IOBJECT (TextGrid, Sound)   // Sound may be NULL
+		autoTextGridEditor editor = TextGridEditor_create (ID_AND_FULL_NAME, me, you, true, nullptr, Melder_peek32to8 (callbackText));
 		Editor_setPublicationCallback (editor.get(), cb_TextGridEditor_publication);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 DIRECT (WINDOW_TextGrid_LongSound_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a TextGrid from batch.");
diff --git a/fon/praat_Tiers.cpp b/fon/praat_Tiers.cpp
index bb6cd00..b2933f0 100644
--- a/fon/praat_Tiers.cpp
+++ b/fon/praat_Tiers.cpp
@@ -1,6 +1,6 @@
 /* praat_Tiers.cpp
  *
- * Copyright (C) 1992-2012,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014,2015,2016,2017 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
@@ -54,17 +54,12 @@ DIRECT (HELP_AmplitudeTier_help) {
 
 DIRECT (WINDOW_AmplitudeTier_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit an AmplitudeTier from batch.");
-	Sound sound = nullptr;
-	LOOP {
-		if (CLASS == classSound) sound = (Sound) OBJECT;   // may stay null
-	}
-	LOOP if (CLASS == classAmplitudeTier) {
-		iam_LOOP (AmplitudeTier);
-		autoAmplitudeTierEditor editor = AmplitudeTierEditor_create (ID_AND_FULL_NAME, me, sound, true);
+	FIND_TWO_WITH_IOBJECT (AmplitudeTier, Sound)   // Sound may be null
+		autoAmplitudeTierEditor editor = AmplitudeTierEditor_create (ID_AND_FULL_NAME, me, you, true);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 DIRECT (HINT_AmplitudeTier_Sound_viewAndEdit) {
 	INFO_NONE
@@ -257,17 +252,12 @@ DIRECT (HELP_DurationTier_help) {
 
 DIRECT (WINDOW_DurationTier_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a DurationTier from batch.");
-	Sound sound = nullptr;
-	LOOP {
-		if (CLASS == classSound) sound = (Sound) OBJECT;   // may stay null
-	}
-	LOOP if (CLASS == classDurationTier) {
-		iam_LOOP (DurationTier);
-		autoDurationTierEditor editor = DurationTierEditor_create (ID_AND_FULL_NAME, me, sound, true);
+	FIND_TWO_WITH_IOBJECT (DurationTier, Sound)   // Sound may be null
+		autoDurationTierEditor editor = DurationTierEditor_create (ID_AND_FULL_NAME, me, you, true);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 DIRECT (HINT_DurationTier_Sound_edit) {
 	INFO_NONE
@@ -391,14 +381,13 @@ static void cb_FormantGridEditor_publish (Editor /* me */, autoDaata publish) {
 }
 DIRECT (WINDOW_FormantGrid_edit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a FormantGrid from batch.");
-	LOOP {
-		iam_LOOP (FormantGrid);
+	FIND_ONE_WITH_IOBJECT (FormantGrid)
 		autoFormantGridEditor editor = FormantGridEditor_create (ID_AND_FULL_NAME, me);
 		Editor_setPublicationCallback (editor.get(), cb_FormantGridEditor_publish);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Modify
 
@@ -620,18 +609,12 @@ DIRECT (HELP_IntensityTier_help) {
 
 DIRECT (WINDOW_IntensityTier_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit an IntensityTier from batch.");
-	Sound sound = nullptr;
-	LOOP {
-		if (CLASS == classSound) sound = (Sound) OBJECT;   // may stay null
-		if (sound) break;   // OPTIMIZE
-	}
-	LOOP if (CLASS == classIntensityTier) {
-		iam_LOOP (IntensityTier);
-		autoIntensityTierEditor editor = IntensityTierEditor_create (ID_AND_FULL_NAME, me, sound, true);
+	FIND_TWO_WITH_IOBJECT (IntensityTier, Sound)   // Sound may be null
+		autoIntensityTierEditor editor = IntensityTierEditor_create (ID_AND_FULL_NAME, me, you, true);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 DIRECT (HINT_IntensityTier_Sound_viewAndEdit) {
 	INFO_NONE
@@ -809,7 +792,7 @@ DO_ALTERNATIVE (GRAPHICS_old_PitchTier_draw)
 
 DIRECT (WINDOW_PitchTier_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a PitchTier from batch.");
-	FIND_TWO (PitchTier, Sound)   // Sound may be null
+	FIND_TWO_WITH_IOBJECT (PitchTier, Sound)   // Sound may be null
 		autoPitchTierEditor editor = PitchTierEditor_create (ID_AND_FULL_NAME, me, you, true);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
@@ -1099,7 +1082,7 @@ DO
 
 DIRECT (WINDOW_PointProcess_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot view or edit a PointProcess from batch.");
-	FIND_TWO (PointProcess, Sound)   // Sound may be null
+	FIND_TWO_WITH_IOBJECT (PointProcess, Sound)   // Sound may be null
 		autoPointEditor editor = PointEditor_create (ID_AND_FULL_NAME, me, you);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
diff --git a/gram/praat_gram.cpp b/gram/praat_gram.cpp
index 0cdfee6..d2bef27 100644
--- a/gram/praat_gram.cpp
+++ b/gram/praat_gram.cpp
@@ -1,6 +1,6 @@
 /* praat_gram.cpp
  *
- * Copyright (C) 1997-2012,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1997-2012,2013,2014,2015,2016,2017 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
@@ -419,13 +419,12 @@ DIRECT (HELP_OTGrammar_help) {
 
 DIRECT (WINDOW_OTGrammar_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot edit from batch.");
-	LOOP {
-		iam (OTGrammar);
+	FIND_ONE_WITH_IOBJECT (OTGrammar)
 		autoOTGrammarEditor editor = OTGrammarEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Draw
 
@@ -1227,13 +1226,12 @@ DO
 
 DIRECT (WINDOW_OTMulti_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot edit an OTMulti from batch.");
-	LOOP {
-		iam (OTMulti);
+	FIND_ONE_WITH_IOBJECT (OTMulti)
 		autoOTMultiEditor editor = OTMultiEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Query
 
diff --git a/kar/UnicodeData.h b/kar/UnicodeData.h
index 30485ab..2b879d4 100644
--- a/kar/UnicodeData.h
+++ b/kar/UnicodeData.h
@@ -30091,6 +30091,10 @@
 #define UNICODE_MATHEMATICAL_MONOSPACE_DIGIT_SEVEN  0x1d7fd
 #define UNICODE_MATHEMATICAL_MONOSPACE_DIGIT_EIGHT  0x1d7fe
 #define UNICODE_MATHEMATICAL_MONOSPACE_DIGIT_NINE  0x1d7ff
+#define UNICODE_SPEAKER_WITH_CANCELLATION_STROKE  0x1f507
+#define UNITEXT_SPEAKER_WITH_CANCELLATION_STROKE U"\U0001f507"
+#define UNICODE_SPEAKER  0x1f508
+#define UNITEXT_SPEAKER  U"\U0001f508"
 #define UNICODE_CJK_COMPATIBILITY_IDEOGRAPH_2F800  0x2f800
 #define UNICODE_CJK_COMPATIBILITY_IDEOGRAPH_2F801  0x2f801
 #define UNICODE_CJK_COMPATIBILITY_IDEOGRAPH_2F802  0x2f802
diff --git a/kar/longchar.cpp b/kar/longchar.cpp
index 4384346..15c49c7 100644
--- a/kar/longchar.cpp
+++ b/kar/longchar.cpp
@@ -551,7 +551,7 @@ static struct structLongchar_Info Longchar_database [] = {
 { '0', '^', 2, 1, { "/ringover",         0, 0,   0,   0,      0, 0,      0, 0,   0,   0   },  42,  42,  42,  42, UNICODE_COMBINING_RING_ABOVE }, // voiceless
 { 'v', '^', 2, 1, { "/caronover",        0, 0,   0,   0,      0, 0,      0, 0,   0,   0   },  38,  38,  38,  38, UNICODE_COMBINING_CARON }, // hacek
 { 'N', '^', 2, 1, { "/breveover",        0, 0,   0,   0,      0, 0,      0, 0,   0,   0   },  40,  40,  40,  40, UNICODE_COMBINING_BREVE }, // nonsyllabic
-{ 'c', 'n', 2, 1, { "/corner",         260, 0,   299, 299,  260, 0,    260, 0,   0,   0   }, 124, 124, 124, 124, UNICODE_COMBINING_LEFT_ANGLE_ABOVE }, // ? unreleased
+{ 'c', 'n', 2, 2, { "/corner",         260, 0,   299, 299,  260, 0,    260, 0,   0,   0   }, 124, 124, 124, 124, UNICODE_COMBINING_LEFT_ANGLE_ABOVE }, // ? unreleased
 { 'c', 'v', 2, 1, { "/halfringleft",     0, 0,   0,   0,      0, 0,      0, 0,   0,   0   },  55,  55,  55,  55, UNICODE_COMBINING_LEFT_HALF_RING_BELOW }, // unrounded
 { 'T', '^', 2, 1, { "/raising",          0, 0,   0,   0,      0, 0,      0, 0,   0,   0   },  51,  51,  51,  51, UNICODE_COMBINING_UP_TACK_BELOW },
 { 'T', 'v', 2, 1, { "/lowering",         0, 0,   0,   0,      0, 0,      0, 0,   0,   0   },  52,  52,  52,  52, UNICODE_COMBINING_DOWN_TACK_BELOW },
diff --git a/main/Makefile b/main/Makefile
index 25684f4..7da2cb8 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -1,9 +1,9 @@
 # Makefile of the 'main' routines.
-# Paul Boersma, 10 September 2011
+# Paul Boersma, 2 June 2017
 
 include ../makefile.defs
 
-CPPFLAGS = -I ../num -I ../sys -I ../fon
+CPPFLAGS = -I ../num -I ../kar -I ../sys -I ../fon
 
 .PHONY: clean
 
diff --git a/main/praat.plist b/main/praat.plist
index d37f9fe..472941b 100644
--- a/main/praat.plist
+++ b/main/praat.plist
@@ -466,7 +466,7 @@
 	<key>CFBundleIconFile</key>
 	<string>Praat</string>
 	<key>CFBundleIdentifier</key>
-	<string>org.praat.Praat</string>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
diff --git a/makefiles/makefile.defs.linux.barren b/makefiles/makefile.defs.linux.barren
index aebdc36..8870bd0 100644
--- a/makefiles/makefile.defs.linux.barren
+++ b/makefiles/makefile.defs.linux.barren
@@ -1,7 +1,7 @@
 # File: makefile.defs.linux.barren
 
 # System: Linux without GUI, network, graphics, and sound
-# Paul Boersma, 26 September 2016
+# Paul Boersma, 22 July 2017
 
 CC = gcc -std=gnu99
 
@@ -13,7 +13,7 @@ CXXFLAGS = $(CFLAGS) -Wshadow
 
 LINK = g++
 
-EXECUTABLE = praat
+EXECUTABLE = praat_barren
 
 LIBS = -lm -lpthread -static -static-libgcc -static-libstdc++
 
@@ -22,4 +22,4 @@ RANLIB = ls
 ICON =
 MAIN_ICON =
 
-INSTALL = cp ./praat /usr/bin
\ No newline at end of file
+INSTALL = cp ./praat_barren /usr/bin
\ No newline at end of file
diff --git a/makefiles/makefile.defs.linux.nogui b/makefiles/makefile.defs.linux.nogui
new file mode 100644
index 0000000..fb0a8f8
--- /dev/null
+++ b/makefiles/makefile.defs.linux.nogui
@@ -0,0 +1,25 @@
+# File: makefile.defs.linux.nogui
+
+# System: Linux without GUI, network, and sound
+# Paul Boersma, 22 July 2017
+
+CC = gcc -std=gnu99
+
+CXX = g++ -std=c++11
+
+CFLAGS = -DNO_GUI -DNO_NETWORK -D_FILE_OFFSET_BITS=64 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/cairo -I/usr/include/pango-1.0 -DUNIX -Dlinux -Werror=missing-prototypes -Werror=implicit -Wreturn-type -Wunused -Wunused-parameter -Wuninitialized -O3 -g1 -pthread
+
+CXXFLAGS = $(CFLAGS) -Wshadow
+
+LINK = g++
+
+EXECUTABLE = praat_nogui
+
+LIBS = -lpangocairo-1.0 -lcairo -lpango-1.0 -lgobject-2.0 -lm -lpthread
+
+AR = ar
+RANLIB = ls
+ICON =
+MAIN_ICON =
+
+INSTALL = cp ./praat_nogui /usr/bin
\ No newline at end of file
diff --git a/num/NUMrandom.cpp b/num/NUMrandom.cpp
index 5c48cbf..c831e86 100644
--- a/num/NUMrandom.cpp
+++ b/num/NUMrandom.cpp
@@ -177,7 +177,7 @@ void NUMrandom_init () {
 		}
 		keys [4] = (uint64_t) (int64) getpid ();   // unique between processes that run simultaneously on the same computer
 		#ifndef _WIN32
-		keys [5] = (uint64_t) (int64) gethostid ();   // unique between computers
+		//keys [5] = (uint64_t) (int64) gethostid ();   // unique between computers; but can be SLOW because it could have to access the internet
 		#endif
 		states [threadNumber]. init_by_array64 (keys, numberOfKeys);
 	}
diff --git a/stat/Regression.cpp b/stat/Regression.cpp
index ad53540..5b3ba46 100644
--- a/stat/Regression.cpp
+++ b/stat/Regression.cpp
@@ -1,6 +1,6 @@
 /* Regression.cpp
  *
- * Copyright (C) 2005-2011,2014,2015,2016 Paul Boersma
+ * Copyright (C) 2005-2011,2014,2015,2016,2017 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
@@ -104,9 +104,11 @@ autoLinearRegression LinearRegression_create () {
 autoLinearRegression Table_to_LinearRegression (Table me) {
 	try {
 		long numberOfIndependentVariables = my numberOfColumns - 1, numberOfParameters = my numberOfColumns;
-		long numberOfCells = my rows.size, icell, ivar;
 		if (numberOfParameters < 1)   // includes intercept
 			Melder_throw (U"Not enough columns (has to be more than 1).");
+		long numberOfCells = my rows.size;
+		if (numberOfCells == 0)
+			Melder_throw (U"Not enough rows (0).");
 		if (numberOfCells < numberOfParameters) {
 			Melder_warning (U"Solution is not unique (more parameters than cases).");
 		}
@@ -114,13 +116,13 @@ autoLinearRegression Table_to_LinearRegression (Table me) {
 		autoNUMvector <double> b (1, numberOfCells);
 		autoNUMvector <double> x (1, numberOfParameters);
 		autoLinearRegression thee = LinearRegression_create ();
-		for (ivar = 1; ivar <= numberOfIndependentVariables; ivar ++) {
+		for (long ivar = 1; ivar <= numberOfIndependentVariables; ivar ++) {
 			double minimum = Table_getMinimum (me, ivar);
 			double maximum = Table_getMaximum (me, ivar);
 			Regression_addParameter (thee.get(), my columnHeaders [ivar]. label, minimum, maximum, 0.0);
 		}
-		for (icell = 1; icell <= numberOfCells; icell ++) {
-			for (ivar = 1; ivar < numberOfParameters; ivar ++) {
+		for (long icell = 1; icell <= numberOfCells; icell ++) {
+			for (long ivar = 1; ivar < numberOfParameters; ivar ++) {
 				u [icell] [ivar] = Table_getNumericValue_Assert (me, icell, ivar);
 			}
 			u [icell] [numberOfParameters] = 1.0;   // for the intercept
@@ -128,7 +130,7 @@ autoLinearRegression Table_to_LinearRegression (Table me) {
 		}
 		NUMsolveEquation (u.peek(), numberOfCells, numberOfParameters, b.peek(), NUMeps * numberOfCells, x.peek());
 		thy intercept = x [numberOfParameters];
-		for (ivar = 1; ivar <= numberOfIndependentVariables; ivar ++) {
+		for (long ivar = 1; ivar <= numberOfIndependentVariables; ivar ++) {
 			RegressionParameter parm = thy parameters.at [ivar];
 			parm -> value = x [ivar];
 		}
diff --git a/stat/Table.cpp b/stat/Table.cpp
index 4f94d74..d53aed5 100644
--- a/stat/Table.cpp
+++ b/stat/Table.cpp
@@ -468,7 +468,7 @@ static void Table_numericize_checkDefined (Table me, long columnNumber) {
 		if (row -> cells [columnNumber]. number == NUMundefined)
 			Melder_throw (me, U": the cell in row ", irow,
 				U" of column \"", my columnHeaders [columnNumber]. label ? my columnHeaders [columnNumber]. label : Melder_integer (columnNumber),
-				U" is undefined.");
+				U"\" is undefined.");
 	}
 }
 
diff --git a/stat/praat_Stat.cpp b/stat/praat_Stat.cpp
index 8173140..bb7d40a 100644
--- a/stat/praat_Stat.cpp
+++ b/stat/praat_Stat.cpp
@@ -1,6 +1,6 @@
 /* praat_Stat.cpp
  *
- * Copyright (C) 1992-2012,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2012,2013,2014,2015,2016,2017 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
@@ -292,13 +292,12 @@ DIRECT (HELP_StatisticsTutorial) {
 
 DIRECT (WINDOW_Table_viewAndEdit) {
 	if (theCurrentPraatApplication -> batch) Melder_throw (U"Cannot edit a Table from batch.");
-	LOOP {
-		iam_LOOP (Table);
+	FIND_ONE_WITH_IOBJECT (Table)
 		autoTableEditor editor = TableEditor_create (ID_AND_FULL_NAME, me);
 		praat_installEditor (editor.get(), IOBJECT);
 		editor.releaseToUser();
-	}
-END }
+	END
+}
 
 // MARK: Tabulate
 
diff --git a/sys/Formula.cpp b/sys/Formula.cpp
index e6b6be1..3fad0d7 100644
--- a/sys/Formula.cpp
+++ b/sys/Formula.cpp
@@ -1,6 +1,6 @@
 /* Formula.cpp
  *
- * Copyright (C) 1992-2011,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1992-2011,2013,2014,2015,2016,2017 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
@@ -43,8 +43,8 @@ static int theLevel = 1;
 static int theExpressionType [1 + MAXIMUM_NUMBER_OF_LEVELS];
 static bool theOptimize;
 
-static struct Formula_NumericVector theZeroNumericVector = { 0, nullptr };
-static struct Formula_NumericMatrix theZeroNumericMatrix = { 0, 0, nullptr };
+static numvec theZeroNumericVector = { 0, nullptr };
+static nummat theZeroNumericMatrix = { 0, 0, nullptr };
 
 typedef struct structFormulaInstruction {
 	int symbol;
@@ -68,7 +68,7 @@ enum { GEENSYMBOOL_,
 /* The list ends with "MINUS_" itself. */
 
 	/* Haakjes-openen. */
-	IF_, THEN_, ELSE_, HAAKJEOPENEN_, RECHTEHAAKOPENEN_, KOMMA_, COLON_, FROM_, TO_,
+	IF_, THEN_, ELSE_, HAAKJEOPENEN_, RECHTEHAAKOPENEN_, OPENING_BRACE_, KOMMA_, COLON_, FROM_, TO_,
 	/* Operatoren met boolean resultaat. */
 	OR_, AND_, NOT_, EQ_, NE_, LE_, LT_, GE_, GT_,
 	/* Operatoren met reeel resultaat. */
@@ -77,7 +77,7 @@ enum { GEENSYMBOOL_,
 /* Then, the symbols after which "-" is binary. */
 
 	/* Haakjes-sluiten. */
-	ENDIF_, FI_, HAAKJESLUITEN_, RECHTEHAAKSLUITEN_,
+	ENDIF_, FI_, HAAKJESLUITEN_, RECHTEHAAKSLUITEN_, CLOSING_BRACE_,
 	/* Dingen met een waarde. */
 	#define LOW_VALUE  NUMBER_
 		NUMBER_, NUMBER_PI_, NUMBER_E_, NUMBER_UNDEFINED_,
@@ -185,7 +185,10 @@ enum { GEENSYMBOOL_,
 	LABEL_,
 	DECREMENT_AND_ASSIGN_, ADD_3DOWN_, POP_2_,
 	NUMERIC_VECTOR_ELEMENT_, NUMERIC_MATRIX_ELEMENT_, VARIABLE_REFERENCE_,
-	SELF0_, SELFSTR0_,
+	NUMERIC_VECTOR_LITERAL_,
+	SELF0_, SELFSTR0_, TO_OBJECT_,
+	OBJECT_XMIN_, OBJECT_XMAX_, OBJECT_YMIN_, OBJECT_YMAX_, OBJECT_NX_, OBJECT_NY_,
+	OBJECT_DX_, OBJECT_DY_, OBJECT_NROW_, OBJECT_NCOL_, OBJECT_ROWSTR_, OBJECT_COLSTR_,
 	OBJECTCELL0_, OBJECTCELLSTR0_, OBJECTCELL1_, OBJECTCELLSTR1_, OBJECTCELL2_, OBJECTCELLSTR2_,
 	OBJECTLOCATION0_, OBJECTLOCATIONSTR0_, OBJECTLOCATION1_, OBJECTLOCATIONSTR1_, OBJECTLOCATION2_, OBJECTLOCATIONSTR2_,
 	SELFMATRIKS1_, SELFMATRIKSSTR1_, SELFMATRIKS2_, SELFMATRIKSSTR2_,
@@ -208,10 +211,10 @@ enum { GEENSYMBOOL_,
 /* they are used in error messages and in debugging (see Formula_print). */
 
 static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
-	U"if", U"then", U"else", U"(", U"[", U",", U":", U"from", U"to",
+	U"if", U"then", U"else", U"(", U"[", U"{", U",", U":", U"from", U"to",
 	U"or", U"and", U"not", U"=", U"<>", U"<=", U"<", U">=", U">",
 	U"+", U"-", U"*", U"/", U"div", U"mod", U"^", U"_call", U"_neg",
-	U"endif", U"fi", U")", U"]",
+	U"endif", U"fi", U")", U"]", U"}",
 	U"a number", U"pi", U"e", U"undefined",
 	U"xmin", U"xmax", U"ymin", U"ymax", U"nx", U"ny", U"dx", U"dy",
 	U"row", U"col", U"nrow", U"ncol", U"row$", U"col$", U"y", U"x",
@@ -276,7 +279,10 @@ static const char32 *Formula_instructionNames [1 + hoogsteSymbool] = { U"",
 	U"_label",
 	U"_decrementAndAssign", U"_add3Down", U"_pop2",
 	U"_numericVectorElement", U"_numericMatrixElement", U"_variableReference",
-	U"_self0", U"_self0$",
+	U"_numericVectorLiteral",
+	U"_self0", U"_self0$", U"_toObject",
+	U"_object_xmin", U"_object_xmax", U"_object_ymin", U"_object_ymax", U"_object_dnx", U"_object_ny",
+	U"_object_dx", U"_object_dy", U"_object_nrow", U"_object_ncol", U"_object_row$", U"_object_col$",
 	U"_objectcell0", U"_objectcell0$", U"_objectcell1", U"_objectcell1$", U"_objectcell2", U"_objectcell2$",
 	U"_objectlocation0", U"_objectlocation0$", U"_objectlocation1", U"_objectlocation1$", U"_objectlocation2", U"_objectlocation2$",
 	U"_selfmatriks1", U"_selfmatriks1$", U"_selfmatriks2", U"_selfmatriks2$",
@@ -389,7 +395,8 @@ static void Formula_lexan () {
 			tokgetal (Melder_atof (token.string));
 		} else if ((kar >= U'a' && kar <= U'z') || kar >= 192 || (kar == U'.' &&
 				((theExpression [ikar + 1] >= U'a' && theExpression [ikar + 1] <= U'z') || theExpression [ikar + 1] >= 192)
-				&& (itok == 0 || (lexan [itok]. symbol != MATRIKS_ && lexan [itok]. symbol != MATRIKSSTR_)))) {
+				&& (itok == 0 || (lexan [itok]. symbol != MATRIKS_ && lexan [itok]. symbol != MATRIKSSTR_
+					&& lexan [itok]. symbol != RECHTEHAAKSLUITEN_)))) {
 			int tok;
 			bool isString = false;
 			int rank = 0;
@@ -797,6 +804,10 @@ static void Formula_lexan () {
 			nieuwtok (RECHTEHAAKOPENEN_)
 		} else if (kar == U']') {
 			nieuwtok (RECHTEHAAKSLUITEN_)
+		} else if (kar == U'{') {
+			nieuwtok (OPENING_BRACE_)
+		} else if (kar == U'}') {
+			nieuwtok (CLOSING_BRACE_)
 		} else if (kar == U'.') {
 			nieuwtok (PERIOD_)
 		} else {
@@ -994,8 +1005,72 @@ static void parsePowerFactor () {
 		symbol = nieuwlees;
 		if (symbol == RECHTEHAAKOPENEN_) {
 			parseExpression ();   // the object's name or ID
+			nieuwontleed (TO_OBJECT_);
 			if (nieuwlees == RECHTEHAAKSLUITEN_) {
-				nieuwontleed (OBJECTCELL0_);
+				symbol = nieuwlees;
+				if (symbol == PERIOD_) {
+					switch (nieuwlees) {
+						case XMIN_:
+							nieuwontleed (OBJECT_XMIN_);
+							return;
+						case XMAX_:
+							nieuwontleed (OBJECT_XMAX_);
+							return;
+						case YMIN_:
+							nieuwontleed (OBJECT_YMIN_);
+							return;
+						case YMAX_:
+							nieuwontleed (OBJECT_YMAX_);
+							return;
+						case NX_:
+							nieuwontleed (OBJECT_NX_);
+							return;
+						case NY_:
+							nieuwontleed (OBJECT_NY_);
+							return;
+						case DX_:
+							nieuwontleed (OBJECT_DX_);
+							return;
+						case DY_:
+							nieuwontleed (OBJECT_DY_);
+							return;
+						case NROW_:
+							nieuwontleed (OBJECT_NROW_);
+							return;
+						case NCOL_:
+							nieuwontleed (OBJECT_NCOL_);
+							return;
+						case ROWSTR_:
+							pas (RECHTEHAAKOPENEN_);
+							parseExpression ();
+							nieuwontleed (OBJECT_ROWSTR_);
+							pas (RECHTEHAAKSLUITEN_);
+							return;
+						case COLSTR_:
+							pas (RECHTEHAAKOPENEN_);
+							parseExpression ();
+							nieuwontleed (OBJECT_COLSTR_);
+							pas (RECHTEHAAKSLUITEN_);
+							return;
+						default:
+							formulefout (U"After \"object [number].\" there should be \"xmin\", \"xmax\", \"ymin\", "
+								"\"ymax\", \"nx\", \"ny\", \"dx\", \"dy\", \"nrow\" or \"ncol\"", lexan [ilexan]. position);
+					}
+				} else if (symbol == RECHTEHAAKOPENEN_) {
+					parseExpression ();
+					if (nieuwlees == KOMMA_) {
+						parseExpression ();
+						nieuwontleed (OBJECTCELL2_);
+						pas (RECHTEHAAKSLUITEN_);
+					} else {
+						oudlees;
+						nieuwontleed (OBJECTCELL1_);
+						pas (RECHTEHAAKSLUITEN_);
+					}
+				} else {
+					oudlees;
+					nieuwontleed (OBJECTCELL0_);
+				}
 			} else {
 				oudlees;
 				pas (KOMMA_);
@@ -1010,8 +1085,9 @@ static void parsePowerFactor () {
 					pas (RECHTEHAAKSLUITEN_);
 				}
 			}
-		} else if (symbol == HAAKJEOPENEN_) {   // the object's name or ID
-			parseExpression ();
+		} else if (symbol == HAAKJEOPENEN_) {
+			parseExpression ();   // the object's name or ID
+			nieuwontleed (TO_OBJECT_);
 			if (nieuwlees == HAAKJESLUITEN_) {
 				nieuwontleed (OBJECTLOCATION0_);
 			} else {
@@ -1038,6 +1114,7 @@ static void parsePowerFactor () {
 		symbol = nieuwlees;
 		if (symbol == RECHTEHAAKOPENEN_) {
 			parseExpression ();   // the object's name or ID
+			nieuwontleed (TO_OBJECT_);
 			if (nieuwlees == RECHTEHAAKSLUITEN_) {
 				nieuwontleed (OBJECTCELLSTR0_);
 			} else {
@@ -1054,8 +1131,9 @@ static void parsePowerFactor () {
 					pas (RECHTEHAAKSLUITEN_);
 				}
 			}
-		} else if (symbol == HAAKJEOPENEN_) {   // the object's name or ID
-			parseExpression ();
+		} else if (symbol == HAAKJEOPENEN_) {
+			parseExpression ();   // the object's name or ID
+			nieuwontleed (TO_OBJECT_);
 			if (nieuwlees == HAAKJESLUITEN_) {
 				nieuwontleed (OBJECTLOCATIONSTR0_);
 			} else {
@@ -1332,6 +1410,20 @@ static void parsePowerFactor () {
 		return;
 	}
 
+	if (symbol == OPENING_BRACE_) {
+		parseExpression ();
+		int n = 1;
+		while (nieuwlees == KOMMA_) {
+			parseExpression ();
+			n ++;
+		}
+		oudlees;
+		pas (CLOSING_BRACE_);
+		nieuwontleed (NUMBER_); parsenumber (n);
+		nieuwontleed (NUMERIC_VECTOR_LITERAL_);
+		return;
+	}
+
 	if (symbol == CALL_) {
 		char32 *procedureName = lexan [ilexan]. content.string;   // reference copy!
 		int n = 0;
@@ -1805,9 +1897,44 @@ static void Formula_optimizeFlow ()
 	}
 }
 
+static int praat_findObjectById (int id) {
+	int IOBJECT;
+	WHERE_DOWN (ID == id)
+		return IOBJECT;
+	Melder_throw (U"No object with number ", id, U".");
+}
+
+static int praat_findObjectFromString (const char32 *name) {
+	int IOBJECT;
+	if (*name >= U'A' && *name <= U'Z') {
+		/*
+		 * Find the object by its name.
+		 */
+		static MelderString buffer { 0 };
+		MelderString_copy (& buffer, name);
+		char32 *space = str32chr (buffer.string, U' ');
+		if (space == nullptr)
+			Melder_throw (U"Missing space in object name \"", name, U"\".");
+		*space = U'\0';
+		char32 *className = & buffer.string [0], *givenName = space + 1;
+		WHERE_DOWN (1) {
+			Daata object = OBJECT;
+			if (str32equ (className, Thing_className (OBJECT)) && str32equ (givenName, object -> name))
+				return IOBJECT;
+		}
+		ClassInfo klas = Thing_classFromClassName (className, nullptr);
+		WHERE_DOWN (1) {
+			Daata object = OBJECT;
+			if (str32equ (klas -> className, Thing_className (OBJECT)) && str32equ (givenName, object -> name))
+				return IOBJECT;
+		}
+	}
+	Melder_throw (U"No object with name \"", name, U"\".");
+}
+
 static void Formula_evaluateConstants () {
 	for (;;) {
-		bool improved = 0;
+		bool improved = false;
 		for (int i = 1; i <= numberOfInstructions; i ++) {
 			int gain = 0;
 			if (parse [i]. symbol == NUMBER_) {
@@ -1826,7 +1953,37 @@ static void Formula_evaluateConstants () {
 						{ gain = 2; parse [i]. content.number *= parse [i + 1]. content.number; }
 					else if (parse [i + 2]. symbol == RDIV_)
 						{ gain = 2; parse [i]. content.number /= parse [i + 1]. content.number; }
+				} else if (parse [i + 1]. symbol == TO_OBJECT_) {
+					parse [i]. symbol = OBJECT_;
+					int IOBJECT = praat_findObjectById (lround (parse [i]. content.number));
+					parse [i]. content.object = OBJECT;
+					gain = 1;
 				}
+			} else if (parse [i]. symbol == STRING_) {
+				if (parse [i + 1]. symbol == TO_OBJECT_) {
+					parse [i]. symbol = OBJECT_;
+					int IOBJECT = praat_findObjectFromString (parse [i]. content.string);
+					parse [i]. content.object = OBJECT;
+					gain = 1;
+				}
+			} else if (parse [i]. symbol == NUMERIC_VARIABLE_) {
+				parse [i]. symbol = NUMBER_;
+				parse [i]. content.number = parse [i]. content.variable -> numericValue;
+				gain = 0;
+				improved = true;
+			} else if (parse [i]. symbol == STRING_VARIABLE_) {
+				parse [i]. symbol = STRING_;
+				parse [i]. content.string = parse [i]. content.variable -> stringValue;   // again a reference copy (lexan is still the owner)
+				gain = 0;
+				improved = true;
+			#if 0
+			} else if (parse [i]. symbol == ROW_) {
+				if (parse [i + 1]. symbol == COL_ && parse [i + 2]. symbol == SELFMATRIKS2_)
+					{ gain = 2; parse [i]. symbol = SELF0_; }   // TODO: SELF0_ may not have the same restrictions as SELFMATRIKS2_
+			} else if (parse [i]. symbol == COL_) {
+				if (parse [i + 1]. symbol == SELFMATRIKS1_)
+					{ gain = 1; parse [i]. symbol = SELF0_; }
+			#endif
 			}
 			if (gain) { improved = true; schuif (i + 1, gain); }
 		}
@@ -1928,7 +2085,7 @@ void Formula_compile (Interpreter interpreter, Daata data, const char32 *express
 	theOptimize = optimize;
 	if (! lexan) {
 		lexan = Melder_calloc_f (struct structFormulaInstruction, 3000);
-		lexan [3000 - 1]. symbol = END_;   /* Make sure that string cleaning always terminates. */
+		lexan [3000 - 1]. symbol = END_;   // make sure that cleaning up always terminates
 	}
 	if (! parse) parse = Melder_calloc_f (struct structFormulaInstruction, 3000);
 
@@ -1941,7 +2098,7 @@ void Formula_compile (Interpreter interpreter, Daata data, const char32 *express
 		for (;;) {
 			int symbol = lexan [ilexan]. symbol;
 			if (symbol == STRING_ || symbol == VARIABLE_NAME_ || symbol == INDEXED_NUMERIC_VARIABLE_ || symbol == INDEXED_STRING_VARIABLE_ || symbol == CALL_) Melder_free (lexan [ilexan]. content.string);
-			else if (symbol == END_) break;   /* Either the end of a formula, or the end of lexan. */
+			else if (symbol == END_) break;   // either the end of a formula, or the end of lexan
 			ilexan ++;
 		}
 		numberOfStringConstants = 0;
@@ -2001,7 +2158,7 @@ static void pushNumericVector (long numberOfElements, double *x) {
 	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
 	if (w > wmax) wmax ++;
 	stackel -> which = Stackel_NUMERIC_VECTOR;
-	stackel -> numericVector.numberOfElements = numberOfElements;
+	stackel -> numericVector.size = numberOfElements;
 	stackel -> numericVector.data = x;
 }
 static void pushNumericMatrix (long numberOfRows, long numberOfColumns, double **x) {
@@ -2009,8 +2166,8 @@ static void pushNumericMatrix (long numberOfRows, long numberOfColumns, double *
 	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
 	if (w > wmax) wmax ++;
 	stackel -> which = Stackel_NUMERIC_MATRIX;
-	stackel -> numericMatrix.numberOfRows = numberOfRows;
-	stackel -> numericMatrix.numberOfColumns = numberOfColumns;
+	stackel -> numericMatrix.nrow = numberOfRows;
+	stackel -> numericMatrix.ncol = numberOfColumns;
 	stackel -> numericMatrix.data = x;
 }
 static void pushString (char32 *x) {
@@ -2020,6 +2177,13 @@ static void pushString (char32 *x) {
 	stackel -> which = Stackel_STRING;
 	stackel -> string = x;
 }
+static void pushObject (Daata object) {
+	Stackel stackel = & theStack [++ w];
+	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
+	if (w > wmax) wmax ++;
+	stackel -> which = Stackel_OBJECT;
+	stackel -> object = object;
+}
 static void pushVariable (InterpreterVariable var) {
 	Stackel stackel = & theStack [++ w];
 	if (stackel -> which > Stackel_NUMBER) Stackel_cleanUp (stackel);
@@ -2034,6 +2198,7 @@ const char32 *Stackel_whichText (Stackel me) {
 		my which == Stackel_NUMERIC_MATRIX ? U"a numeric matrix" :
 		my which == Stackel_STRING ? U"a string" :
 		my which == Stackel_STRING_ARRAY ? U"a string array" :
+		my which == Stackel_OBJECT ? U"an object" :
 		U"???";
 }
 
@@ -2134,7 +2299,7 @@ static void do_add () {
 			/*
 				result# = x + y#
 			*/
-			long ny = y->numericVector.numberOfElements;
+			long ny = y->numericVector.size;
 			double *result = NUMvector<double> (1, ny);
 			if (xvalue == NUMundefined) {
 				for (long i = 1; i <= ny; i ++) {
@@ -2153,7 +2318,7 @@ static void do_add () {
 			/*
 				result## = x + y##
 			*/
-			long nrow = y->numericMatrix.numberOfRows, ncol = y->numericMatrix.numberOfColumns;
+			long nrow = y->numericMatrix.nrow, ncol = y->numericMatrix.ncol;
 			double **result = NUMmatrix<double> (1, nrow, 1, ncol);
 			if (xvalue == NUMundefined) {
 				for (long irow = 1; irow <= nrow; irow ++) {
@@ -2174,7 +2339,7 @@ static void do_add () {
 		}
 	}
 	if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_VECTOR) {
-		long nx = x->numericVector.numberOfElements, ny = y->numericVector.numberOfElements;
+		long nx = x->numericVector.size, ny = y->numericVector.size;
 		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);
@@ -2187,8 +2352,8 @@ static void do_add () {
 		return;
 	}
 	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;
+		long xnrow = x->numericMatrix.nrow, xncol = x->numericMatrix.ncol;
+		long ynrow = y->numericMatrix.nrow, yncol = y->numericMatrix.ncol;
 		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)
@@ -2233,7 +2398,7 @@ static void do_sub () {
 			/*
 				result# = x - y#
 			*/
-			long ny = y->numericVector.numberOfElements;
+			long ny = y->numericVector.size;
 			double *result = NUMvector<double> (1, ny);
 			if (xvalue == NUMundefined) {
 				for (long i = 1; i <= ny; i ++) {
@@ -2252,7 +2417,7 @@ static void do_sub () {
 			/*
 				result## = x - y##
 			*/
-			long nrow = y->numericMatrix.numberOfRows, ncol = y->numericMatrix.numberOfColumns;
+			long nrow = y->numericMatrix.nrow, ncol = y->numericMatrix.ncol;
 			double **result = NUMmatrix<double> (1, nrow, 1, ncol);
 			if (xvalue == NUMundefined) {
 				for (long irow = 1; irow <= nrow; irow ++) {
@@ -2273,7 +2438,7 @@ static void do_sub () {
 		}
 	}
 	if (x->which == Stackel_NUMERIC_VECTOR && y->which == Stackel_NUMERIC_VECTOR) {
-		long nx = x->numericVector.numberOfElements, ny = y->numericVector.numberOfElements;
+		long nx = x->numericVector.size, ny = y->numericVector.size;
 		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);
@@ -2286,8 +2451,8 @@ static void do_sub () {
 		return;
 	}
 	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;
+		long xnrow = x->numericMatrix.nrow, xncol = x->numericMatrix.ncol;
+		long ynrow = y->numericMatrix.nrow, yncol = y->numericMatrix.ncol;
 		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)
@@ -2337,7 +2502,7 @@ static void do_mul () {
 			/*
 				result# = x * y#
 			*/
-			long ny = y->numericVector.numberOfElements;
+			long ny = y->numericVector.size;
 			double *result = NUMvector<double> (1, ny);
 			if (xvalue == NUMundefined) {
 				for (long i = 1; i <= ny; i ++) {
@@ -2356,7 +2521,7 @@ static void do_mul () {
 			/*
 				result## = x * y##
 			*/
-			long nrow = y->numericMatrix.numberOfRows, ncol = y->numericMatrix.numberOfColumns;
+			long nrow = y->numericMatrix.nrow, ncol = y->numericMatrix.ncol;
 			double **result = NUMmatrix<double> (1, nrow, 1, ncol);
 			if (xvalue == NUMundefined) {
 				for (long irow = 1; irow <= nrow; irow ++) {
@@ -2380,7 +2545,7 @@ static void do_mul () {
 		/*
 			result# = x# * y#
 		*/
-		long nx = x->numericVector.numberOfElements, ny = y->numericVector.numberOfElements;
+		long nx = x->numericVector.size, ny = y->numericVector.size;
 		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);
@@ -2404,7 +2569,7 @@ static void do_rdiv () {
 	}
 	if (x->which == Stackel_NUMERIC_VECTOR) {
 		if (y->which == Stackel_NUMERIC_VECTOR) {
-			long nelem1 = x->numericVector.numberOfElements, nelem2 = y->numericVector.numberOfElements;
+			long nelem1 = x->numericVector.size, nelem2 = y->numericVector.size;
 			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);
@@ -2417,7 +2582,7 @@ static void do_rdiv () {
 			/*
 				result# = x# / y
 			*/
-			long xn = x->numericVector.numberOfElements;
+			long xn = x->numericVector.size;
 			double *result = NUMvector<double> (1, xn);
 			double yvalue = y->number;
 			if (yvalue == 0.0) {
@@ -2505,7 +2670,7 @@ static void do_functionvec_n_n (double (*f) (double)) {
 	#else
 	Stackel x = & theStack [w];
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long nelm = x->numericVector.numberOfElements;
+		long nelm = x->numericVector.size;
 		for (long i = 1; i <= nelm; i ++) {
 			x->numericVector.data [i] = f (x->numericVector.data [i]);
 		}
@@ -2518,7 +2683,7 @@ static void do_functionvec_n_n (double (*f) (double)) {
 static void do_softmax () {
 	Stackel x = & theStack [w];
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long nelm = x->numericVector.numberOfElements;
+		long nelm = x->numericVector.size;
 		double maximum = -1e308;
 		for (long i = 1; i <= nelm; i ++) {
 			if (x->numericVector.data [i] > maximum) {
@@ -2584,7 +2749,7 @@ static void do_rectify () {
 static void do_rectify_numvec () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long nelm = x->numericVector.numberOfElements;
+		long nelm = x->numericVector.size;
 		double *result = NUMvector<double> (1, nelm);
 		for (long i = 1; i <= nelm; i ++) {
 			double xvalue = x->numericVector.data [i];
@@ -2665,7 +2830,7 @@ static void do_exp () {
 static void do_exp_numvec () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long nelm = x->numericVector.numberOfElements;
+		long nelm = x->numericVector.size;
 		double *result = NUMvector<double> (1, nelm);
 		for (long i = 1; i <= nelm; i ++) {
 			result [i] = exp (x->numericVector.data [i]);
@@ -2678,7 +2843,7 @@ static void do_exp_numvec () {
 static void do_exp_nummat () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_MATRIX) {
-		long nrow = x->numericMatrix.numberOfRows, ncol = x->numericMatrix.numberOfColumns;
+		long nrow = x->numericMatrix.nrow, ncol = x->numericMatrix.ncol;
 		double **result = NUMmatrix<double> (1, nrow, 1, ncol);
 		for (long irow = 1; irow <= nrow; irow ++) {
 			for (long icol = 1; icol <= ncol; icol ++) {
@@ -2744,7 +2909,7 @@ static void do_log10 () {
 static void do_sum () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long numberOfElements = x->numericVector.numberOfElements;
+		long numberOfElements = x->numericVector.size;
 		double result = 0.0;
 		for (long i = 1; i <= numberOfElements; i ++) {
 			result += x->numericVector.data [i];
@@ -2757,7 +2922,7 @@ static void do_sum () {
 static void do_mean () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long numberOfElements = x->numericVector.numberOfElements;
+		long numberOfElements = x->numericVector.size;
 		double result = 0.0;
 		for (long i = 1; i <= numberOfElements; i ++) {
 			result += x->numericVector.data [i];
@@ -2771,7 +2936,7 @@ static void do_mean () {
 static void do_stdev () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long numberOfElements = x->numericVector.numberOfElements;
+		long numberOfElements = x->numericVector.size;
 		double mean = 0.0;
 		for (long i = 1; i <= numberOfElements; i ++) {
 			mean += x->numericVector.data [i];
@@ -2792,7 +2957,7 @@ static void do_stdev () {
 static void do_center () {
 	Stackel x = pop;
 	if (x->which == Stackel_NUMERIC_VECTOR) {
-		long numberOfElements = x->numericVector.numberOfElements;
+		long numberOfElements = x->numericVector.size;
 		double result = 0.0, sumOfWeights = 0.0;
 		for (long i = 1; i <= numberOfElements; i ++) {
 			result += i * x->numericVector.data [i];
@@ -2822,7 +2987,7 @@ static void do_function_dd_d_numvec (double (*f) (double, double)) {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
 	Stackel y = pop, x = pop, a = pop;
 	if (a->which == Stackel_NUMERIC_VECTOR && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		long numberOfElements = a->numericVector.numberOfElements;
+		long numberOfElements = a->numericVector.size;
 		double *newData = NUMvector <double> (1, numberOfElements);
 		for (long ielem = 1; ielem <= numberOfElements; ielem ++) {
 			newData [ielem] = f (x->number, y->number);
@@ -2841,8 +3006,8 @@ static void do_function_dd_d_nummat (double (*f) (double, double)) {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
 	Stackel y = pop, x = pop, a = pop;
 	if (a->which == Stackel_NUMERIC_MATRIX && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		long numberOfRows = a->numericMatrix.numberOfRows;
-		long numberOfColumns = a->numericMatrix.numberOfColumns;
+		long numberOfRows = a->numericMatrix.nrow;
+		long numberOfColumns = a->numericMatrix.ncol;
 		double **newData = NUMmatrix <double> (1, numberOfRows, 1, numberOfColumns);
 		for (long irow = 1; irow <= numberOfRows; irow ++) {
 			for (long icol = 1; icol <= numberOfColumns; icol ++) {
@@ -2863,7 +3028,7 @@ static void do_function_ll_l_numvec (long (*f) (long, long)) {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
 	Stackel y = pop, x = pop, a = pop;
 	if (a->which == Stackel_NUMERIC_VECTOR && x->which == Stackel_NUMBER) {
-		long numberOfElements = a->numericVector.numberOfElements;
+		long numberOfElements = a->numericVector.size;
 		double *newData = NUMvector <double> (1, numberOfElements);
 		for (long ielem = 1; ielem <= numberOfElements; ielem ++) {
 			newData [ielem] = f (lround (x->number), lround (y->number));
@@ -2882,8 +3047,8 @@ static void do_function_ll_l_nummat (long (*f) (long, long)) {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol], U" requires three arguments.");
 	Stackel y = pop, x = pop, a = pop;
 	if (a->which == Stackel_NUMERIC_MATRIX && x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		long numberOfRows = a->numericMatrix.numberOfRows;
-		long numberOfColumns = a->numericMatrix.numberOfColumns;
+		long numberOfRows = a->numericMatrix.nrow;
+		long numberOfColumns = a->numericMatrix.ncol;
 		double **newData = NUMmatrix <double> (1, numberOfRows, 1, numberOfColumns);
 		for (long irow = 1; irow <= numberOfRows; irow ++) {
 			for (long icol = 1; icol <= numberOfColumns; icol ++) {
@@ -2930,17 +3095,6 @@ static void do_function_ll_l (long (*f) (long, long)) {
 			Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
 	}
 }
-static void do_function_dl_l (long (*f) (double, long)) {
-	Stackel y = pop, x = pop;
-	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
-		pushNumber (x->number == NUMundefined || y->number == NUMundefined ? NUMundefined :
-			f (x->number, lround (y->number)));
-	} else {
-		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
-			U" requires two numeric arguments, not ",
-			Stackel_whichText (x), U" and ", Stackel_whichText (y), U".");
-	}
-}
 static void do_objects_are_identical () {
 	Stackel y = pop, x = pop;
 	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
@@ -2970,17 +3124,6 @@ static void do_function_ddd_d (double (*f) (double, double, double)) {
 			Stackel_whichText (y), U", and ", Stackel_whichText (z), U".");
 	}
 }
-static void do_function_dll_d (double (*f) (double, long, long)) {
-	Stackel z = pop, y = pop, x = pop;
-	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER && z->which == Stackel_NUMBER) {
-		pushNumber (x->number == NUMundefined || y->number == NUMundefined || z->number == NUMundefined ? NUMundefined :
-			f (x->number, lround (y->number), lround (z->number)));
-	} else {
-		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
-			U" requires three numeric arguments, not ", Stackel_whichText (x), U", ",
-			Stackel_whichText (y), U", and ", Stackel_whichText (z), U".");
-	}
-}
 static void do_do () {
 	Stackel narg = pop;
 	Melder_assert (narg->which == Stackel_NUMBER);
@@ -3087,14 +3230,14 @@ static void shared_do_writeInfo (int numberOfArguments) {
 		} else if (arg->which == Stackel_STRING) {
 			MelderInfo_write (arg->string);
 		} else if (arg->which == Stackel_NUMERIC_VECTOR) {
-			long numberOfElements = arg->numericVector.numberOfElements;
+			long numberOfElements = arg->numericVector.size;
 			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;
+			long numberOfRows = arg->numericMatrix.nrow;
+			long numberOfColumns = arg->numericMatrix.ncol;
 			double **data = arg->numericMatrix.data;
 			for (long irow = 1; irow <= numberOfRows; irow ++) {
 				for (long icol = 1; icol <= numberOfRows; icol ++) {
@@ -3457,7 +3600,7 @@ static void do_imax () {
 	} 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 numberOfElements = last->numericVector.size;
 		long result = 1;
 		double maximum = last->numericVector.data [1];
 		for (long i = 2; i <= numberOfElements; i ++) {
@@ -3568,7 +3711,7 @@ static void do_numberOfRows () {
 		Melder_throw (U"The function \"numberOfRows\" requires one argument.");
 	Stackel array = pop;
 	if (array->which == Stackel_NUMERIC_MATRIX) {
-		pushNumber (array->numericMatrix.numberOfRows);
+		pushNumber (array->numericMatrix.nrow);
 	} else {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
 			U" requires a matrix argument, not ", Stackel_whichText (array), U".");
@@ -3581,7 +3724,7 @@ static void do_numberOfColumns () {
 		Melder_throw (U"The function \"numberOfColumns\" requires one argument.");
 	Stackel array = pop;
 	if (array->which == Stackel_NUMERIC_MATRIX) {
-		pushNumber (array->numericMatrix.numberOfColumns);
+		pushNumber (array->numericMatrix.ncol);
 	} else {
 		Melder_throw (U"The function ", Formula_instructionNames [parse [programPointer]. symbol],
 			U" requires a matrix argument, not ", Stackel_whichText (array), U".");
@@ -3638,7 +3781,7 @@ static void do_numericVectorElement () {
 	element = lround (r -> number);
 	if (element <= 0)
 		Melder_throw (U"In vector indexing, the element index has to be positive.");
-	if (element > vector -> numericVectorValue. numberOfElements)
+	if (element > vector -> numericVectorValue. size)
 		Melder_throw (U"Element index out of bounds.");
 	pushNumber (vector -> numericVectorValue. data [element]);
 }
@@ -3653,7 +3796,7 @@ static void do_numericMatrixElement () {
 	column = lround (c -> number);
 	if (column <= 0)
 		Melder_throw (U"In matrix indexing, the column index has to be positive.");
-	if (column > matrix -> numericMatrixValue. numberOfColumns)
+	if (column > matrix -> numericMatrixValue. ncol)
 		Melder_throw (U"Column index out of bounds.");
 	Stackel r = pop;
 	if (r -> which != Stackel_NUMBER)
@@ -3663,7 +3806,7 @@ static void do_numericMatrixElement () {
 	row = lround (r -> number);
 	if (row <= 0)
 		Melder_throw (U"In matrix indexing, the row index has to be positive.");
-	if (row > matrix -> numericMatrixValue. numberOfRows)
+	if (row > matrix -> numericMatrixValue. nrow)
 		Melder_throw (U"Row index out of bounds.");
 	pushNumber (matrix -> numericMatrixValue. data [row] [column]);
 }
@@ -4100,39 +4243,6 @@ static void do_numberOfSelected () {
 	}
 	pushNumber (result);
 }
-static int praat_findObjectById (int id) {
-	int IOBJECT;
-	WHERE_DOWN (ID == id)
-		return IOBJECT;
-	Melder_throw (U"No object with number ", id, U".");
-}
-static int praat_findObjectFromString (const char32 *name) {
-	int IOBJECT;
-	if (*name >= U'A' && *name <= U'Z') {
-		/*
-		 * Find the object by its name.
-		 */
-		static MelderString buffer { 0 };
-		MelderString_copy (& buffer, name);
-		char32 *space = str32chr (buffer.string, U' ');
-		if (space == nullptr)
-			Melder_throw (U"Missing space in object name \"", name, U"\".");
-		*space = U'\0';
-		char32 *className = & buffer.string [0], *givenName = space + 1;
-		WHERE_DOWN (1) {
-			Daata object = OBJECT;
-			if (str32equ (className, Thing_className (OBJECT)) && str32equ (givenName, object -> name))
-				return IOBJECT;
-		}
-		ClassInfo klas = Thing_classFromClassName (className, nullptr);
-		WHERE_DOWN (1) {
-			Daata object = OBJECT;
-			if (str32equ (klas -> className, Thing_className (OBJECT)) && str32equ (givenName, object -> name))
-				return IOBJECT;
-		}
-	}
-	Melder_throw (U"No object with name \"", name, U"\".");
-}
 static void do_selectObject () {
 	Stackel n = pop;
 	praat_deselectAll ();
@@ -4202,6 +4312,214 @@ static void do_removeObject () {
 	praat_show ();
 	pushNumber (1);
 }
+static void do_object_xmin () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetXmin ()) {
+			pushNumber (data -> v_getXmin ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"xmin\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].xmin\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_xmax () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetXmax ()) {
+			pushNumber (data -> v_getXmax ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"xmax\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].xmax\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_ymin () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetYmin ()) {
+			pushNumber (data -> v_getYmin ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"ymin\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].ymin\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_ymax () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetYmax ()) {
+			pushNumber (data -> v_getYmax ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"ymax\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].ymax\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_nx () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetNx ()) {
+			pushNumber (data -> v_getNx ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"nx\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].nx\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_ny () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetNy ()) {
+			pushNumber (data -> v_getNy ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"ny\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].ny\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_dx () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetDx ()) {
+			pushNumber (data -> v_getDx ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"dx\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].dx\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_dy () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetDy ()) {
+			pushNumber (data -> v_getDy ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"dy\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].dy\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_nrow () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetNrow ()) {
+			pushNumber (data -> v_getNrow ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"nrow\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].nrow\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_ncol () {
+	Stackel object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetNcol ()) {
+			pushNumber (data -> v_getNcol ());
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"ncol\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].ncol\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_rowstr () {
+	Stackel index = pop, object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetRowStr ()) {
+			if (index -> which == Stackel_NUMBER) {
+				long number = lround (index->number);
+				autostring32 result = Melder_dup (data -> v_getRowStr (number));
+				if (! result.peek())
+					Melder_throw (U"Row index out of bounds.");
+				pushString (result.transfer());
+			} else {
+				Melder_throw (U"The expression \"object[].row$[xx]\" requires xx to be a number, not ", Stackel_whichText (index), U".");
+			}
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"row$[]\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].row$[]\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
+static void do_object_colstr () {
+	Stackel index = pop, object = pop;
+	if (object -> which == Stackel_NUMBER || object -> which == Stackel_STRING) {
+		int IOBJECT = object -> which == Stackel_NUMBER ?
+			praat_findObjectById (lround (object -> number)) :
+			praat_findObjectFromString (object -> string);
+		Daata data = OBJECT;
+		if (data -> v_hasGetColStr ()) {
+			if (index -> which == Stackel_NUMBER) {
+				long number = lround (index->number);
+				autostring32 result = Melder_dup (data -> v_getColStr (number));
+				if (! result.peek())
+					Melder_throw (U"Column index out of bounds.");
+				pushString (result.transfer());
+			} else {
+				Melder_throw (U"The expression \"object[].col$[xx]\" requires xx to be a number, not ", Stackel_whichText (index), U".");
+			}
+		} else {
+			Melder_throw (U"An object of type ", Thing_className (data), U" has no \"col$[]\" attribute.");
+		}
+	} else {
+		Melder_throw (U"The expression \"object[xx].col$[]\" requires xx to be a number or a string, not ", Stackel_whichText (object), U".");
+	}
+}
 static void do_stringStr () {
 	Stackel value = pop;
 	if (value->which == Stackel_NUMBER) {
@@ -4299,13 +4617,27 @@ static void do_readFileStr () {
 		Melder_throw (U"The function \"readFile$\" requires a string (a file name), not ", Stackel_whichText (f), U".");
 	}
 }
+static void do_numericVectorLiteral () {
+	Stackel n = pop;
+	Melder_assert (n->which == Stackel_NUMBER);
+	long numberOfElements = lround (n->number);
+	Melder_assert (numberOfElements > 0);
+	double *result = NUMvector<double> (1, numberOfElements);
+	for (long ielement = numberOfElements; ielement > 0; ielement --) {
+		Stackel e = pop;
+		if (e->which != Stackel_NUMBER)
+			Melder_throw (U"Vector element has to be a number, not ", Stackel_whichText (e));
+		result [ielement] = e->number;
+	}
+	pushNumericVector (numberOfElements, result);
+}
 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;
+		long xn = x->numericVector.size, yn = y->numericVector.size;
 		double **result = NUMmatrix<double> (1, xn, 1, yn);
 		for (long irow = 1; irow <= xn; irow ++) {
 			double xvalue = x->numericVector.data [irow];
@@ -4334,7 +4666,7 @@ static void do_mulNumvec () {
 		/*
 			result# = mul# (x#, y##)
 		*/
-		long xn = x->numericVector.numberOfElements, ynrow = y->numericMatrix.numberOfRows, yncol = y->numericMatrix.numberOfColumns;
+		long xn = x->numericVector.size, ynrow = y->numericMatrix.nrow, yncol = y->numericMatrix.ncol;
 		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);
@@ -4372,7 +4704,7 @@ static void do_mulNumvec () {
 		/*
 			result# = mul# (x##, y#)
 		*/
-		long xnrow = x->numericMatrix.numberOfRows, xncol = x->numericMatrix.numberOfColumns, yn = y->numericVector.numberOfElements;
+		long xnrow = x->numericMatrix.nrow, xncol = x->numericMatrix.ncol, yn = y->numericVector.size;
 		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);
@@ -4995,7 +5327,8 @@ static void do_selfStr0 (long irow, long icol) {
 		Melder_throw (Thing_className (me), U" objects (like self) accept no [] indexing.");
 	}
 }
-static Daata getObjectFromUniqueID (Stackel object) {
+static void do_toObject () {
+	Stackel object = pop;
 	Daata thee = nullptr;
 	if (object->which == Stackel_NUMBER) {
 		int i = theCurrentPraatObjects -> n;
@@ -5016,10 +5349,11 @@ static Daata getObjectFromUniqueID (Stackel object) {
 	} else {
 		Melder_throw (U"The first argument to \"object\" must be a number (unique ID) or a string (name), not ", Stackel_whichText (object), U".");
 	}
-	return thee;
+	pushObject (thee);
 }
 static void do_objectCell0 (long irow, long icol) {
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel object = pop;
+	Daata thee = object->object;
 	if (thy v_hasGetCell ()) {
 		pushNumber (thy v_getCell ());
 	} else if (thy v_hasGetVector ()) {
@@ -5119,8 +5453,8 @@ static void do_selfMatriksStr1 (long irow) {
 	}
 }
 static void do_objectCell1 (long irow) {
-	Stackel column = pop;
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel column = pop, object = pop;
+	Daata thee = object->object;
 	long icol = Stackel_getColumnNumber (column, thee);
 	if (thy v_hasGetVector ()) {
 		pushNumber (thy v_getVector (irow, icol));
@@ -5155,8 +5489,8 @@ static void do_matriks1 (long irow) {
 	}
 }
 static void do_objectCellStr1 (long irow) {
-	Stackel column = pop;
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel column = pop, object = pop;
+	Daata thee = object->object;
 	long icol = Stackel_getColumnNumber (column, thee);
 	if (thy v_hasGetVectorStr ()) {
 		autostring32 result = Melder_dup (thy v_getVectorStr (icol));
@@ -5216,8 +5550,8 @@ static void do_selfMatriksStr2 () {
 	pushString (result.transfer());
 }
 static void do_objectCell2 () {
-	Stackel column = pop, row = pop;
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel column = pop, row = pop, object = pop;
+	Daata thee = object->object;
 	long irow = Stackel_getRowNumber (row, thee);
 	long icol = Stackel_getColumnNumber (column, thee);
 	if (! thy v_hasGetMatrix ())
@@ -5234,8 +5568,8 @@ static void do_matriks2 () {
 	pushNumber (thy v_getMatrix (irow, icol));
 }
 static void do_objectCellStr2 () {
-	Stackel column = pop, row = pop;
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel column = pop, row = pop, object = pop;
+	Daata thee = object->object;
 	long irow = Stackel_getRowNumber (row, thee);
 	long icol = Stackel_getColumnNumber (column, thee);
 	if (! thy v_hasGetMatrixStr ())
@@ -5254,7 +5588,8 @@ static void do_matriksStr2 () {
 	pushString (result.transfer());
 }
 static void do_objectLocation0 (long irow, long icol) {
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel object = pop;
+	Daata thee = object->object;
 	if (thy v_hasGetFunction0 ()) {
 		pushNumber (thy v_getFunction0 ());
 	} else if (thy v_hasGetFunction1 ()) {
@@ -5350,8 +5685,8 @@ static void do_selfFunktie1 (long irow) {
 	}
 }
 static void do_objectLocation1 (long irow) {
-	Stackel x = pop;
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel x = pop, object = pop;
+	Daata thee = object->object;
 	if (x->which == Stackel_NUMBER) {
 		if (thy v_hasGetFunction1 ()) {
 			pushNumber (thy v_getFunction1 (irow, x->number));
@@ -5412,8 +5747,8 @@ static void do_selfFunktie2 () {
 	}
 }
 static void do_objectLocation2 () {
-	Stackel y = pop, x = pop;
-	Daata thee = getObjectFromUniqueID (pop);
+	Stackel y = pop, x = pop, object = pop;
+	Daata thee = object->object;
 	if (x->which == Stackel_NUMBER && y->which == Stackel_NUMBER) {
 		if (! thy v_hasGetFunction2 ())
 			Melder_throw (Thing_className (thee), U" objects accept no (x, y) values.");
@@ -5565,7 +5900,7 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case RANDOM_UNIFORM_: { do_function_dd_d (NUMrandomUniform);
 } break; case RANDOM_INTEGER_: { do_function_ll_l (NUMrandomInteger);
 } break; case RANDOM_GAUSS_: { do_function_dd_d (NUMrandomGauss);
-} break; case RANDOM_BINOMIAL_: { do_function_dl_l (NUMrandomBinomial);
+} break; case RANDOM_BINOMIAL_: { do_function_dl_d (NUMrandomBinomial_real);
 } break; case CHI_SQUARE_P_: { do_function_dd_d (NUMchiSquareP);
 } break; case CHI_SQUARE_Q_: { do_function_dd_d (NUMchiSquareQ);
 } break; case INCOMPLETE_GAMMAP_: { do_function_dd_d (NUMincompleteGammaP);
@@ -5653,6 +5988,18 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case PLUS_OBJECT_  : { do_plusObject   ();
 } break; case MINUS_OBJECT_ : { do_minusObject  ();
 } break; case REMOVE_OBJECT_: { do_removeObject ();
+} break; case OBJECT_XMIN_: { do_object_xmin ();
+} break; case OBJECT_XMAX_: { do_object_xmax ();
+} break; case OBJECT_YMIN_: { do_object_ymin ();
+} break; case OBJECT_YMAX_: { do_object_ymax ();
+} break; case OBJECT_NX_: { do_object_nx ();
+} break; case OBJECT_NY_: { do_object_ny ();
+} break; case OBJECT_DX_: { do_object_dx ();
+} break; case OBJECT_DY_: { do_object_dy ();
+} break; case OBJECT_NROW_: { do_object_nrow ();
+} break; case OBJECT_NCOL_: { do_object_ncol ();
+} break; case OBJECT_ROWSTR_: { do_object_rowstr ();
+} break; case OBJECT_COLSTR_: { do_object_colstr ();
 } break; case STRINGSTR_: { do_stringStr ();
 } break; case SLEEP_: { do_sleep ();
 } break; case FIXEDSTR_: { do_fixedStr ();
@@ -5766,6 +6113,8 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 	pushVariable (var);
 } break; case SELF0_: { do_self0 (row, col);
 } break; case SELFSTR0_: { do_selfStr0 (row, col);
+} break; case OBJECT_: { pushObject (f [programPointer]. content.object);
+} break; case TO_OBJECT_: { do_toObject ();
 } break; case SELFMATRIKS1_: { do_selfMatriks1 (row);
 } break; case SELFMATRIKSSTR1_: { do_selfMatriksStr1 (row);
 } break; case SELFMATRIKS2_: { do_selfMatriks2 ();
@@ -5794,19 +6143,20 @@ case NUMBER_: { pushNumber (f [programPointer]. content.number);
 } break; case STRING_: {
 	autostring32 string = Melder_dup (f [programPointer]. content.string);
 	pushString (string.transfer());
+} break; case NUMERIC_VECTOR_LITERAL_: { do_numericVectorLiteral ();
 } break; case NUMERIC_VARIABLE_: {
 	InterpreterVariable var = f [programPointer]. content.variable;
 	pushNumber (var -> numericValue);
 } break; case NUMERIC_VECTOR_VARIABLE_: {
 	InterpreterVariable var = f [programPointer]. content.variable;
 	double *data = NUMvector_copy (var -> numericVectorValue. data,
-		1, var -> numericVectorValue. numberOfElements);
-	pushNumericVector (var -> numericVectorValue. numberOfElements, data);
+		1, var -> numericVectorValue. size);
+	pushNumericVector (var -> numericVectorValue. size, data);
 } break; case NUMERIC_MATRIX_VARIABLE_: {
 	InterpreterVariable var = f [programPointer]. content.variable;
 	double **data = NUMmatrix_copy (var -> numericMatrixValue. data,
-		1, var -> numericMatrixValue. numberOfRows, 1, var -> numericMatrixValue. numberOfColumns);
-	pushNumericMatrix (var -> numericMatrixValue. numberOfRows, var -> numericMatrixValue. numberOfColumns, data);
+		1, var -> numericMatrixValue. nrow, 1, var -> numericMatrixValue. ncol);
+	pushNumericMatrix (var -> numericMatrixValue. nrow, var -> numericMatrixValue. ncol, data);
 } break; case STRING_VARIABLE_: {
 	InterpreterVariable var = f [programPointer]. content.variable;
 	autostring32 string = Melder_dup (var -> stringValue);
diff --git a/sys/Formula.h b/sys/Formula.h
index a3e688b..02bf250 100644
--- a/sys/Formula.h
+++ b/sys/Formula.h
@@ -2,7 +2,7 @@
 #define _Formula_h_
 /* Formula.h
  *
- * Copyright (C) 1990-2011,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1990-2011,2013,2014,2015,2016,2017 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
@@ -19,6 +19,7 @@
  */
 
 #include "Data.h"
+#include "tensor.h"
 
 #define kFormula_EXPRESSION_TYPE_NUMERIC  0
 #define kFormula_EXPRESSION_TYPE_STRING  1
@@ -29,16 +30,6 @@
 #define kFormula_EXPRESSION_TYPE_STRING_ARRAY  6
 #define kFormula_EXPRESSION_TYPE_UNKNOWN  7
 
-struct Formula_NumericVector {
-	long numberOfElements;
-	double *data;
-};
-
-struct Formula_NumericMatrix {
-	long numberOfRows, numberOfColumns;
-	double **data;
-};
-
 Thing_declare (InterpreterVariable);
 
 typedef struct structStackel {
@@ -50,12 +41,14 @@ typedef struct structStackel {
 	#define Stackel_NUMERIC_TENSOR4  5
 	#define Stackel_STRING_ARRAY  6
 	#define Stackel_VARIABLE  -1
+	#define Stackel_OBJECT  -2
 	int which;   /* 0 or negative = no clean-up required, positive = requires clean-up */
 	union {
 		double number;
 		char32 *string;
-		struct Formula_NumericVector numericVector;
-		struct Formula_NumericMatrix numericMatrix;
+		Daata object;
+		numvec numericVector;
+		nummat numericMatrix;
 		InterpreterVariable variable;
 	};
 } *Stackel;
@@ -66,8 +59,8 @@ struct Formula_Result {
 	union {
 		double numericResult;
 		char32 *stringResult;
-		struct Formula_NumericVector numericVectorResult;
-		struct Formula_NumericMatrix numericMatrixResult;
+		numvec numericVectorResult;
+		nummat numericMatrixResult;
 	} result;
 };
 
diff --git a/sys/Graphics.cpp b/sys/Graphics.cpp
index f38a48f..d70479a 100644
--- a/sys/Graphics.cpp
+++ b/sys/Graphics.cpp
@@ -85,6 +85,8 @@ void Graphics_init (Graphics me, int resolution) {
 		my resolutionNumber = kGraphics_resolution_600;
 	} else if (resolution == 720) {
 		my resolutionNumber = kGraphics_resolution_720;
+	} else if (resolution == 900) {
+		my resolutionNumber = kGraphics_resolution_900;
 	} else if (resolution == 1200) {
 		my resolutionNumber = kGraphics_resolution_1200;
 	} else {
diff --git a/sys/Graphics.h b/sys/Graphics.h
index 3a1dcd9..dc870bf 100644
--- a/sys/Graphics.h
+++ b/sys/Graphics.h
@@ -19,6 +19,7 @@
  */
 
 #include "Thing.h"
+#include "longchar.h"
 
 #include "Graphics_enums.h"
 
@@ -34,6 +35,7 @@ typedef struct {
 	double size_real;
 	unsigned long code;
 	char32 kar;
+	Longchar_Info karInfo;
 	double width;
 	union { long integer; const char *string; void *pointer; } font;
 	int cell, line, run;
@@ -379,9 +381,5 @@ void Graphics_nextSheetOfPaper (Graphics me);
 
 void Graphics_prefs ();
 
-#if defined (UNIX)
-	#define USE_PANGO  1
-#endif
-
 /* End of file Graphics.h */
 #endif
diff --git a/sys/GraphicsP.h b/sys/GraphicsP.h
index ae7b0e3..3e5fb10 100644
--- a/sys/GraphicsP.h
+++ b/sys/GraphicsP.h
@@ -41,12 +41,15 @@ void Graphics_init (Graphics me, int resolution);
 #define kGraphics_font_CHINESE  (kGraphics_font_MAX + 5)
 #define kGraphics_font_JAPANESE  (kGraphics_font_MAX + 6)
 
+/*
+	Honour the NO_GRAPHICS compiler switch.
+*/
 #if defined (NO_GRAPHICS)
 	#define cairo 0
 	#define gdi 0
 	#define direct2d 0
 	#define quartz 0
-#elif gtk
+#elif defined (UNIX)   /* include graphics even if there is no GUI, in order to be able to save PNG files */
 	#define cairo 1   /* Cairo, including Pango */
 	#define gdi 0
 	#define direct2d 0
@@ -61,6 +64,14 @@ void Graphics_init (Graphics me, int resolution);
 	#define gdi 0
 	#define direct2d 0
 	#define quartz 1   /* Quartz, including CoreText */
+#else
+	/*
+		Unknown platforms have no graphics.
+	*/
+	#define cairo 0
+	#define gdi 0
+	#define direct2d 0
+	#define quartz 0
 #endif
 
 Thing_define (GraphicsScreen, Graphics) {
@@ -68,12 +79,14 @@ Thing_define (GraphicsScreen, Graphics) {
 	structMelderFile d_file;
 	#if defined (NO_GRAPHICS)
 	#elif cairo
-		GdkDisplay *d_display;
-		#if ALLOW_GDK_DRAWING
-			GdkDrawable *d_window;
-			GdkGC *d_gdkGraphicsContext;
-		#else
-			GdkWindow *d_window;
+		#if gtk
+			GdkDisplay *d_display;
+			#if ALLOW_GDK_DRAWING
+				GdkDrawable *d_window;
+				GdkGC *d_gdkGraphicsContext;
+			#else
+				GdkWindow *d_window;
+			#endif
 		#endif
 		cairo_surface_t *d_cairoSurface;
 		cairo_t *d_cairoGraphicsContext;
diff --git a/sys/GraphicsScreen.cpp b/sys/GraphicsScreen.cpp
index 1a3a39a..a48c01b 100644
--- a/sys/GraphicsScreen.cpp
+++ b/sys/GraphicsScreen.cpp
@@ -211,7 +211,7 @@ void structGraphicsScreen :: v_destroy () noexcept {
 }
 
 void structGraphicsScreen :: v_flushWs () {
-	#if cairo
+	#if cairo && gtk
 		// Ik weet niet of dit is wat het zou moeten zijn ;)
 		//gdk_window_process_updates (d_window, true);   // this "works" but is incorrect because it's not the expose events that have to be carried out
 		//gdk_window_flush (d_window);
@@ -241,7 +241,7 @@ void Graphics_flushWs (Graphics me) {
 }
 
 void structGraphicsScreen :: v_clearWs () {
-	#if cairo
+	#if cairo && gtk
 		GdkRectangle rect;
 		if (our d_x1DC < our d_x2DC) {
 			rect.x = our d_x1DC;
@@ -277,7 +277,7 @@ void structGraphicsScreen :: v_clearWs () {
 		/*if (d_winWindow) SendMessage (d_winWindow, WM_ERASEBKGND, (WPARAM) d_gdiGraphicsContext, 0);*/
 	#elif quartz
         GuiCocoaDrawingArea *cocoaDrawingArea = (GuiCocoaDrawingArea *) d_drawingArea -> d_widget;
-        if (cocoaDrawingArea) {
+        if (cocoaDrawingArea && ! [cocoaDrawingArea isHiddenOrHasHiddenAncestor]) {   // can be called at destruction time
             NSRect rect;
             if (our d_x1DC < our d_x2DC) {
                 rect.origin.x = our d_x1DC;
@@ -328,57 +328,58 @@ void structGraphicsScreen :: v_updateWs () {
 	 * the idea is to generate an expose event to which the drawing area will
 	 * respond by redrawing its contents from the (changed) data.
 	 */
-	#if cairo
-		//GdkWindow *window = gtk_widget_get_parent_window (GTK_WIDGET (my drawingArea -> d_widget));
+	#if cairo && gtk
+		//GdkWindow *window = gtk_widget_get_parent_window (GTK_WIDGET (our d_drawingArea -> d_widget));
 		GdkRectangle rect;
 
-		if (this -> d_x1DC < this -> d_x2DC) {
-			rect.x = this -> d_x1DC;
-			rect.width = this -> d_x2DC - this -> d_x1DC;
+		if (our d_x1DC < our d_x2DC) {
+			rect.x = our d_x1DC;
+			rect.width = our d_x2DC - our d_x1DC;
 		} else {
-			rect.x = this -> d_x2DC;
-			rect.width = this -> d_x1DC - this -> d_x2DC;
+			rect.x = our d_x2DC;
+			rect.width = our d_x1DC - our d_x2DC;
 		}
 
-		if (this -> d_y1DC < this -> d_y2DC) {
-			rect.y = this -> d_y1DC;
-			rect.height = this -> d_y2DC - this -> d_y1DC;
+		if (our d_y1DC < our d_y2DC) {
+			rect.y = our d_y1DC;
+			rect.height = our d_y2DC - our d_y1DC;
 		} else {
-			rect.y = this -> d_y2DC;
-			rect.height = this -> d_y1DC - this -> d_y2DC;
+			rect.y = our d_y2DC;
+			rect.height = our d_y1DC - our d_y2DC;
 		}
 
-		if (d_cairoGraphicsContext && d_drawingArea) {  // update clipping rectangle to new graphics size
-			cairo_reset_clip (d_cairoGraphicsContext);
-			cairo_rectangle (d_cairoGraphicsContext, rect.x, rect.y, rect.width, rect.height);
-			cairo_clip (d_cairoGraphicsContext);
+		if (our d_cairoGraphicsContext && our d_drawingArea) {  // update clipping rectangle to new graphics size
+			cairo_reset_clip (our d_cairoGraphicsContext);
+			cairo_rectangle (our d_cairoGraphicsContext, rect.x, rect.y, rect.width, rect.height);
+			cairo_clip (our d_cairoGraphicsContext);
 		}
 		#if ALLOW_GDK_DRAWING
-			gdk_window_clear (d_window);
+			gdk_window_clear (our d_window);
 		#endif
-		gdk_window_invalidate_rect (d_window, & rect, true);
-		//gdk_window_process_updates (d_window, true);
+		gdk_window_invalidate_rect (our d_window, & rect, true);
+		//gdk_window_process_updates (our d_window, true);
 	#elif gdi
 		//clear (this); // lll
-		if (d_winWindow) InvalidateRect (d_winWindow, nullptr, true);
+		if (our d_winWindow) InvalidateRect (our d_winWindow, nullptr, true);
 	#elif quartz
-        NSView *view =  d_macView;
+        NSView *view = our d_macView;
+		Melder_assert (!! view);
         NSRect rect;
     
-        if (this -> d_x1DC < this -> d_x2DC) {
-            rect.origin.x = this -> d_x1DC;
-            rect.size.width = this -> d_x2DC - this -> d_x1DC;
+        if (our d_x1DC < our d_x2DC) {
+            rect.origin.x = our d_x1DC;
+            rect.size.width = our d_x2DC - our d_x1DC;
         } else {
-            rect.origin.x = this -> d_x2DC;
-            rect.size.width = this -> d_x1DC - this -> d_x2DC;
+            rect.origin.x = our d_x2DC;
+            rect.size.width = our d_x1DC - our d_x2DC;
         }
         
-        if (this -> d_y1DC < this -> d_y2DC) {
-            rect.origin.y = this -> d_y1DC;
-            rect.size.height = this -> d_y2DC - this -> d_y1DC;
+        if (our d_y1DC < our d_y2DC) {
+            rect.origin.y = our d_y1DC;
+            rect.size.height = our d_y2DC - our d_y1DC;
         } else {
-            rect.origin.y = this -> d_y2DC;
-            rect.size.height = this -> d_y1DC - this -> d_y2DC;
+            rect.origin.y = our d_y2DC;
+            rect.size.height = our d_y1DC - our d_y2DC;
         }
     
         //[view setNeedsDisplayInRect: rect];
@@ -424,7 +425,7 @@ static int GraphicsScreen_init (GraphicsScreen me, void *voidDisplay, void *void
 
 	/* Fill in new members. */
 
-	#if cairo
+	#if cairo && gtk
 		my d_display = (GdkDisplay *) gdk_display_get_default ();
 		_GraphicsScreen_text_init (me);
 		#if ALLOW_GDK_DRAWING
@@ -526,7 +527,7 @@ autoGraphics Graphics_create_screenPrinter (void *display, void *window) {
 autoGraphics Graphics_create_xmdrawingarea (GuiDrawingArea w) {
 	trace (U"begin");
 	autoGraphicsScreen me = Thing_new (GraphicsScreen);
-	#if cairo
+	#if cairo && gtk
 		GtkRequisition realsize;
 		GtkAllocation allocation;
 	#elif gdi
@@ -544,7 +545,7 @@ autoGraphics Graphics_create_xmdrawingarea (GuiDrawingArea w) {
 	#elif quartz
 		_GraphicsMacintosh_tryToInitializeQuartz ();
 	#endif
-	#if cairo
+	#if cairo && gtk
 		Graphics_init (me.get(), Gui_getResolution (my d_drawingArea -> d_widget));
 		GraphicsScreen_init (me.get(), GTK_WIDGET (my d_drawingArea -> d_widget), GTK_WIDGET (my d_drawingArea -> d_widget));
 	#elif gdi
@@ -555,7 +556,7 @@ autoGraphics Graphics_create_xmdrawingarea (GuiDrawingArea w) {
 		GraphicsScreen_init (me.get(), my d_drawingArea -> d_widget, my d_drawingArea -> d_widget);
 	#endif
 
-	#if cairo
+	#if cairo && gtk
 		// fb: is really the request meant or rather the actual size, aka allocation?
 		gtk_widget_size_request (GTK_WIDGET (my d_drawingArea -> d_widget), & realsize);
 		gtk_widget_get_allocation (GTK_WIDGET (my d_drawingArea -> d_widget), & allocation);
diff --git a/sys/Graphics_colour.cpp b/sys/Graphics_colour.cpp
index 129704a..275dc4c 100644
--- a/sys/Graphics_colour.cpp
+++ b/sys/Graphics_colour.cpp
@@ -389,11 +389,11 @@ void Graphics_xorOn (Graphics graphics, Graphics_Colour colour) {
 	if (graphics -> screen) {
 		GraphicsScreen me = static_cast <GraphicsScreen> (graphics);
 		#if cairo
-			GdkColor colourXorWhite { 0,
-				(uint16) ((uint16) (colour. red   * 65535.0) ^ (uint16) 0xFFFF),
-				(uint16) ((uint16) (colour. green * 65535.0) ^ (uint16) 0xFFFF),
-				(uint16) ((uint16) (colour. blue  * 65535.0) ^ (uint16) 0xFFFF) };
 			#if ALLOW_GDK_DRAWING
+				GdkColor colourXorWhite { 0,
+					(uint16) ((uint16) (colour. red   * 65535.0) ^ (uint16) 0xFFFF),
+					(uint16) ((uint16) (colour. green * 65535.0) ^ (uint16) 0xFFFF),
+					(uint16) ((uint16) (colour. blue  * 65535.0) ^ (uint16) 0xFFFF) };
 				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & colourXorWhite);
 				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_XOR);
 				gdk_flush ();
@@ -418,8 +418,8 @@ void Graphics_xorOff (Graphics graphics) {
 	if (graphics -> screen) {
 		GraphicsScreen me = static_cast <GraphicsScreen> (graphics);
 		#if cairo
-			GdkColor black { 0, 0x0000, 0x0000, 0x0000 };
 			#if ALLOW_GDK_DRAWING
+				GdkColor black { 0, 0x0000, 0x0000, 0x0000 };
 				gdk_gc_set_rgb_fg_color (my d_gdkGraphicsContext, & black);
 				gdk_gc_set_function (my d_gdkGraphicsContext, GDK_COPY);
 				gdk_flush ();   // to undraw the last drawing
diff --git a/sys/Graphics_linesAndAreas.cpp b/sys/Graphics_linesAndAreas.cpp
index f37248c..5f0a7a3 100644
--- a/sys/Graphics_linesAndAreas.cpp
+++ b/sys/Graphics_linesAndAreas.cpp
@@ -584,18 +584,20 @@ void structGraphicsScreen :: v_button (double x1DC, double x2DC, double y1DC, do
 		if (x2DC <= x1DC || y1DC <= y2DC) return;
 		
 		cairo_save (d_cairoGraphicsContext);
-		if (d_drawingArea && 0) {
-			// clip to drawing area
-			int w, h;
-			#if ALLOW_GDK_DRAWING
-				gdk_drawable_get_size (d_window, & w, & h);
-			#else
-				w = gdk_window_get_width (d_window);
-				h = gdk_window_get_height (d_window);
-			#endif
-			cairo_rectangle (d_cairoGraphicsContext, 0, 0, w, h);
-			cairo_clip (d_cairoGraphicsContext);
-		}
+		#if 0
+			if (d_drawingArea) {
+				// clip to drawing area
+				int w, h;
+				#if ALLOW_GDK_DRAWING
+					gdk_drawable_get_size (d_window, & w, & h);
+				#else
+					w = gdk_window_get_width (d_window);
+					h = gdk_window_get_height (d_window);
+				#endif
+				cairo_rectangle (d_cairoGraphicsContext, 0, 0, w, h);
+				cairo_clip (d_cairoGraphicsContext);
+			}
+		#endif
 		cairo_set_line_width (d_cairoGraphicsContext, 1.0);
 		double left = x1DC - 0.5, right = x2DC - 0.5, top = y2DC + 0.5, bottom = y1DC + 0.5;
 		double width = right - left, height = bottom - top;
diff --git a/sys/Graphics_mouse.cpp b/sys/Graphics_mouse.cpp
index 76a251f..c5ac667 100644
--- a/sys/Graphics_mouse.cpp
+++ b/sys/Graphics_mouse.cpp
@@ -26,7 +26,7 @@
  */
 
 bool structGraphicsScreen :: v_mouseStillDown () {
-	#if cairo
+	#if cairo && gtk
 		Graphics_flushWs (this);
 		GdkEvent *gevent = gdk_display_get_event (d_display);
 		if (! gevent) return true;
@@ -56,7 +56,7 @@ bool Graphics_mouseStillDown (Graphics me) {
 }
 
 void structGraphicsScreen :: v_getMouseLocation (double *p_xWC, double *p_yWC) {
-	#if cairo
+	#if cairo && gtk
 		gint xDC, yDC;
 		gdk_window_get_pointer (d_window, & xDC, & yDC, nullptr);
 		Graphics_DCtoWC (this, xDC, yDC, p_xWC, p_yWC);
diff --git a/sys/Graphics_text.cpp b/sys/Graphics_text.cpp
index 62dad74..e54f302 100644
--- a/sys/Graphics_text.cpp
+++ b/sys/Graphics_text.cpp
@@ -31,11 +31,13 @@ extern const char * ipaSerifRegularPS [];
  * The slant correction is taken relative to the font size.
  */
 #define POSTSCRIPT_SLANT_CORRECTION  0.1
-#define SLANT_CORRECTION  POSTSCRIPT_SLANT_CORRECTION
+#define SCREEN_SLANT_CORRECTION  0.05
 
 #define HAS_FI_AND_FL_LIGATURES  ( my postScript == true )
 
 #if cairo
+	PangoFontMap *thePangoFontMap;
+	PangoContext *thePangoContext;
 	static bool hasTimes, hasHelvetica, hasCourier, hasSymbol, hasPalatino, hasDoulos, hasCharis, hasIpaSerif;
 #elif gdi
 	#define win_MAXIMUM_FONT_SIZE  500
@@ -121,88 +123,155 @@ extern const char * ipaSerifRegularPS [];
 	}
 #endif
 
-#if cairo && USE_PANGO
+#if cairo
 	static PangoFontDescription *PangoFontDescription_create (int font, _Graphics_widechar *lc) {
-		const char *fontFace =
-			font == kGraphics_font_HELVETICA ? "Helvetica" :
-			font == kGraphics_font_TIMES ? "Times" :
-			font == kGraphics_font_COURIER ? "Courier" : 
-			font == kGraphics_font_PALATINO ? "Palatino" : 
-			font == kGraphics_font_IPATIMES ? "Doulos SIL" :
-			font == kGraphics_font_IPAPALATINO ? "Charis SIL" :
-			font == kGraphics_font_DINGBATS ? "Dingbats" : "Serif";
-		PangoFontDescription *font_description = pango_font_description_from_string (fontFace);
-
-		//fprintf (stderr, "%s || %s\n", fontFace, pango_font_description_get_family (font_description));
+		static PangoFontDescription *fontDescriptions [1 + kGraphics_font_DINGBATS];
+		Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
+		if (! fontDescriptions [font]) {
+			const char *fontFace =
+				font == kGraphics_font_HELVETICA ? "Helvetica" :
+				font == kGraphics_font_TIMES ? "Times" :
+				font == kGraphics_font_COURIER ? "Courier" : 
+				font == kGraphics_font_PALATINO ? "Palatino" : 
+				font == kGraphics_font_IPATIMES ? "Doulos SIL" :
+				font == kGraphics_font_IPAPALATINO ? "Charis SIL" :
+				font == kGraphics_font_DINGBATS ? "Dingbats" : "Serif";
+			fontDescriptions [font] = pango_font_description_from_string (fontFace);
+		}
 
 		PangoStyle slant = (lc -> style & Graphics_ITALIC ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
-		pango_font_description_set_style (font_description, slant);
+		pango_font_description_set_style (fontDescriptions [font], slant);
 						
 		PangoWeight weight = (lc -> style & Graphics_BOLD ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
-		pango_font_description_set_weight (font_description, weight);
-		pango_font_description_set_absolute_size (font_description, (int) (lc -> size * PANGO_SCALE));
-		return font_description;
+		pango_font_description_set_weight (fontDescriptions [font], weight);
+		pango_font_description_set_absolute_size (fontDescriptions [font], (int) (lc -> size * PANGO_SCALE));
+		return fontDescriptions [font];
 	}
 #endif
 
-#if quartz || cairo
-	inline static int chooseFont (Graphics me, Longchar_Info info, _Graphics_widechar *lc) {
-		int font =
-			info -> alphabet == Longchar_SYMBOL || // ? kGraphics_font_SYMBOL :
-			info -> alphabet == Longchar_PHONETIC ?
-				( my font == kGraphics_font_TIMES ?
-					( hasDoulos ?
-						( lc -> style == 0 ?
-							kGraphics_font_IPATIMES :
-						  hasCharis ?
-							kGraphics_font_IPAPALATINO :   // other styles in Charis, because Doulos has no bold or italic
-							kGraphics_font_TIMES
-						) :
-					  hasCharis ?
-						kGraphics_font_IPAPALATINO :
-						kGraphics_font_TIMES   // on newer systems, Times and Times New Roman have a lot of phonetic characters
-					) :
-				  my font == kGraphics_font_HELVETICA || my font == kGraphics_font_COURIER ?
-					my font :   // sans serif or wide, so fall back on Lucida Grande for phonetic characters
-				  /* my font must be kGraphics_font_PALATINO */
-				  hasCharis && Melder_debug != 900 ?
-					kGraphics_font_IPAPALATINO :
-				  hasDoulos && Melder_debug != 900 ?
-					( lc -> style == 0 ?
-						kGraphics_font_IPATIMES :
-						kGraphics_font_TIMES
-					) :
-					kGraphics_font_PALATINO
-				) :
-			lc -> kar == '/' ?
-				kGraphics_font_PALATINO :   // override Courier
-			info -> alphabet == Longchar_DINGBATS ?
-				kGraphics_font_DINGBATS :
-			lc -> font.integer == kGraphics_font_COURIER ?
-				kGraphics_font_COURIER :
-			my font == kGraphics_font_TIMES ?
+inline static bool isDiacritic (Longchar_Info info, int font) {
+	if (info -> isDiacritic == 0) return false;
+	if (info -> isDiacritic == 1) return true;
+	Melder_assert (info -> isDiacritic == 2);   // corner
+	if (font == kGraphics_font_IPATIMES || font == kGraphics_font_IPAPALATINO) return false;   // Doulos or Charis
+	return true;   // e.g. Times substitutes a zero-width corner
+}
+
+/*
+	Most operating systems provide automatic font substitution nowadays,
+	so that e.g. phonetic characters will be drawn recognizably even if the
+	user-preferred font is e.g. Times, Helvetica, Courier or Palatino
+	and the phonetic characters are not available in that user font.
+	
+	However, on some systems the substituted font might really be a last resort
+	font that does not look at all similar to the user font. An example is
+	the use of the sans-serif font Lucida Grande for phonetic characters
+	within a stretch of a serif font such as Times or Palatino.
+	
+	This is not good enough for Praat. We need more control over the shape
+	of phonetic characters. We therefore advise the use of Doulos SIL,
+	which is Times-like, or Charis SIL, which is Palatino-like.
+	For true continuity between non-phonetic and phonetic characters it is
+	mandatory that the exact same font is used for both types of characters,
+	so we use Doulos SIL to replace Times even for non-phonetic characters,
+	and Charis SIL to replace Palatino even for non-phonetic characters.
+	A technical issue that makes this even more important is that diacritics
+	can look really weird if at the beginning of a Praat font stretch:
+	a "b" followed by a ring below will not be aligned correctly if they
+	are part of different Praat font stretches.
+	
+	Beside Praat-enforced visual font continuity, there are some more issues,
+	such as that the "/" (slash) character should extend below the baseline
+	whenever it is used in an equation or to demarcate a phonological
+	representation.
+*/
+#if cairo || quartz
+inline static int chooseFont (Graphics me, _Graphics_widechar *lc) {
+	/*
+		When we arrive here, the character's font is the "user-preferred font",
+		which is Courier if the user asked for code style (e.g. between "$$" and "$"),
+		or otherwise Times, Helvetica, Courier or Palatino, as chosen from a font menu.
+		
+		Exception: if the character is a slash, its font has already been converted to Courier.
+	*/
+	int font = lc -> font.integer;
+	Longchar_Info info = lc -> karInfo;
+	int alphabet = info -> alphabet;
+
+	if (font == kGraphics_font_COURIER) {
+		constexpr bool systemSubstitutesMonospacedSerifFontForIpaCourier = (quartz);
+		if (systemSubstitutesMonospacedSerifFontForIpaCourier) {
+			/*
+				No need to check whether the character is phonetic or not.
+			*/
+			return kGraphics_font_COURIER;
+		}
+		if (alphabet == Longchar_SYMBOL ||
+			alphabet == Longchar_PHONETIC ||
+			lc [1]. karInfo -> isDiacritic)   // inspect next character to ensure diacritic continuity
+		{
+			/*
+				Serif is more important than monospaced,
+				and Charis looks slightly better within Courier than Doulos does.
+			*/
+			if (hasCharis) return kGraphics_font_IPAPALATINO;
+			if (hasDoulos) return kGraphics_font_IPATIMES;
+		}
+		return kGraphics_font_COURIER;
+	}
+	font =
+		alphabet == Longchar_SYMBOL || // ? kGraphics_font_SYMBOL :
+		alphabet == Longchar_PHONETIC ?
+			( my font == kGraphics_font_TIMES ?
 				( hasDoulos ?
 					( lc -> style == 0 ?
 						kGraphics_font_IPATIMES :
-					  lc -> style == Graphics_ITALIC ?
-						kGraphics_font_TIMES :
 					  hasCharis ?
-						kGraphics_font_IPAPALATINO :
-						kGraphics_font_TIMES 
+						kGraphics_font_IPAPALATINO :   // other styles in Charis, because Doulos has no bold or italic
+						kGraphics_font_TIMES
 					) :
+				  hasCharis ?
+					kGraphics_font_IPAPALATINO :
+					kGraphics_font_TIMES   // on newer systems, Times and Times New Roman have a lot of phonetic characters
+				) :
+			  my font == kGraphics_font_HELVETICA ?
+				kGraphics_font_HELVETICA :   // sans serif, so fall back on Lucida Grande or so for phonetic characters
+			  /* my font must be kGraphics_font_PALATINO */
+			  hasCharis && Melder_debug != 900 ?
+				kGraphics_font_IPAPALATINO :
+			  hasDoulos && Melder_debug != 900 ?
+				( lc -> style == 0 ?
+					kGraphics_font_IPATIMES :
 					kGraphics_font_TIMES
-				) :   // needed for correct placement of diacritics
-			my font == kGraphics_font_HELVETICA ?
-				kGraphics_font_HELVETICA :
-			my font == kGraphics_font_PALATINO ?
-				( hasCharis && Melder_debug != 900 ?
+				) :
+				kGraphics_font_PALATINO
+			) :
+		alphabet == Longchar_DINGBATS ?
+			kGraphics_font_DINGBATS :
+		my font == kGraphics_font_TIMES ?
+			( hasDoulos ?
+				( lc -> style == 0 ?
+					kGraphics_font_IPATIMES :
+				  lc -> style == Graphics_ITALIC ?
+					( lc [1]. karInfo -> isDiacritic && hasCharis ?
+						kGraphics_font_IPAPALATINO : kGraphics_font_TIMES ) :   // correct placement of diacritics
+				  hasCharis ?
 					kGraphics_font_IPAPALATINO :
-					kGraphics_font_PALATINO
+					kGraphics_font_TIMES 
 				) :
-			my font;   // why not lc -> font.integer?
-		Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
-		return font;
-	}
+				kGraphics_font_TIMES
+			) :
+		my font == kGraphics_font_HELVETICA ?
+			kGraphics_font_HELVETICA :
+		my font == kGraphics_font_PALATINO ?
+			( hasCharis && Melder_debug != 900 ?
+				kGraphics_font_IPAPALATINO :
+				kGraphics_font_PALATINO
+			) :
+		my font;   // why not lc -> font.integer?
+	Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
+	return font;
+}
 #endif
 
 static void charSize (void *void_me, _Graphics_widechar *lc) {
@@ -210,102 +279,19 @@ static void charSize (void *void_me, _Graphics_widechar *lc) {
 	if (my screen) {
 		iam (GraphicsScreen);
 		#if cairo
-			if (my duringXor) {
-				Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
-				int normalSize = my fontSize * my resolution / 72.0;
-				int smallSize = (3 * normalSize + 2) / 4;
-				int size = lc -> size < 100 ? smallSize : normalSize;
-				lc -> width = 10;
-				lc -> baseline *= my fontSize * 0.01;
-				lc -> code = lc -> kar;
-				lc -> font.string = nullptr;
-				lc -> font.integer = 0;
-				lc -> size = size;
-			} else {
-			#if USE_PANGO
-				if (! my d_cairoGraphicsContext) return;
-				Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
-				double normalSize = my fontSize * my resolution / 72.0;
-				double smallSize = (3 * normalSize + 2) / 4;
-				double size = lc -> size < 100 ? smallSize : normalSize;
-				char32 buffer [2] = { lc -> kar, 0 };
-				int font = chooseFont (me, info, lc);
-
-				lc -> size = (int) size;   // an approximation, but needed for testing equality
-				lc -> size_real = size;   // the accurate measurement
-
-				PangoFontDescription *font_description = PangoFontDescription_create (font, lc);
-
-				PangoFontMap *pango_font_map = pango_cairo_font_map_get_default ();
-				PangoContext *pango_context = pango_font_map_create_context (pango_font_map);
-
-				PangoAttribute *pango_attribute = pango_attr_font_desc_new (font_description);
-				PangoAttrList *pango_attr_list = pango_attr_list_new ();
-				pango_attr_list_insert (pango_attr_list, pango_attribute); // list is owner of attribute
-				PangoAttrIterator *pango_attr_iterator = pango_attr_list_get_iterator (pango_attr_list);
-				int length = strlen (Melder_peek32to8 (buffer));
-				GList *pango_glist = pango_itemize (pango_context, Melder_peek32to8 (buffer), 0, length, pango_attr_list, pango_attr_iterator);
-				PangoAnalysis pango_analysis = ((PangoItem *) pango_glist -> data) -> analysis;
-				PangoGlyphString *pango_glyph_string = pango_glyph_string_new ();
-				pango_shape (Melder_peek32to8 (buffer), length, & pango_analysis, pango_glyph_string);
-				
-				lc -> width = Longchar_Info_isDiacritic (info) ? 0 :
-					pango_glyph_string_get_width (pango_glyph_string) / PANGO_SCALE;
-				trace (U"width ", lc -> width);
-				lc -> code = lc -> kar;
-				lc -> baseline *= my fontSize * 0.01;
-				lc -> font.string = nullptr;
-				lc -> font.integer = font;
-				pango_glyph_string_free (pango_glyph_string);
-				g_list_free_full (pango_glist, (GDestroyNotify) pango_item_free);
-				//g_list_free (pango_glist);
-				pango_attr_iterator_destroy (pango_attr_iterator);
-				pango_attr_list_unref (pango_attr_list);
-				//pango_attribute_destroy (pango_attribute); // list is owner
-				g_object_unref (pango_context);
-				//g_object_unref (pango_font_map);   // from the Pango manual: "should not be freed"
-
-			#else
-				if (! my d_cairoGraphicsContext) return;
-				Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
-				int font, size, style;
-				int normalSize = my fontSize * my resolution / 72.0;
-				int smallSize = (3 * normalSize + 2) / 4;
-				size = lc -> size < 100 ? smallSize : normalSize;
-				cairo_text_extents_t extents;
-
-				enum _cairo_font_slant slant   = (lc -> style & Graphics_ITALIC ? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL);
-				enum _cairo_font_weight weight = (lc -> style & Graphics_BOLD   ? CAIRO_FONT_WEIGHT_BOLD  : CAIRO_FONT_WEIGHT_NORMAL);
-
-				cairo_set_font_size (my d_cairoGraphicsContext, size);
-				
-				font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
-					   info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
-					   info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : lc -> font.integer;
-
-				switch (font) {
-					case kGraphics_font_HELVETICA: cairo_select_font_face (my d_cairoGraphicsContext, "Helvetica", slant, weight); break;
-					case kGraphics_font_TIMES:     cairo_select_font_face (my d_cairoGraphicsContext, "Times New Roman", slant, weight); break;
-					case kGraphics_font_COURIER:   cairo_select_font_face (my d_cairoGraphicsContext, "Courier", slant, weight); break;
-					case kGraphics_font_PALATINO:  cairo_select_font_face (my d_cairoGraphicsContext, "Palatino", slant, weight); break;
-					case kGraphics_font_SYMBOL:    cairo_select_font_face (my d_cairoGraphicsContext, "Symbol", slant, weight); break;
-					case kGraphics_font_IPATIMES:  cairo_select_font_face (my d_cairoGraphicsContext, "Doulos SIL", slant, weight); break;
-					case kGraphics_font_DINGBATS:  cairo_select_font_face (my d_cairoGraphicsContext, "Dingbats", slant, weight); break;
-					default:                       cairo_select_font_face (my d_cairoGraphicsContext, "Sans", slant, weight); break;
-				}
-				char32 buffer [2] = { lc -> kar, 0 };
-				cairo_text_extents (my d_cairoGraphicsContext, Melder_peek32to8 (buffer), & extents);
-				lc -> width = extents.x_advance;
-				trace (U"width ", lc -> width);
-				lc -> baseline *= my fontSize * 0.01;
-				lc -> code = lc -> kar;
-				lc -> font.string = nullptr;
-				lc -> font.integer = font;
-				lc -> size = size;
-			#endif
-			}
+			Melder_assert (my duringXor);
+			Longchar_Info info = lc -> karInfo;
+			int normalSize = my fontSize * my resolution / 72.0;
+			int smallSize = (3 * normalSize + 2) / 4;
+			int size = lc -> size < 100 ? smallSize : normalSize;
+			lc -> width = 7;
+			lc -> baseline *= my fontSize * 0.01;
+			lc -> code = lc -> kar;
+			lc -> font.string = nullptr;
+			lc -> font.integer = 0;
+			lc -> size = size;
 		#elif gdi
-			Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
+			Longchar_Info info = lc -> karInfo;
 			int font, size, style;
 			HFONT fontInfo;
 			int normalSize = win_size2isize (my fontSize);
@@ -371,7 +357,7 @@ static void charSize (void *void_me, _Graphics_widechar *lc) {
 	} else if (my postScript) {
 		iam (GraphicsPostscript);
 		int normalSize = (int) ((double) my fontSize * (double) my resolution / 72.0);
-		Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
+		Longchar_Info info = lc -> karInfo;
 		int font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
 				info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
 				info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : lc -> font.integer;
@@ -575,11 +561,90 @@ static void charDraw (void *void_me, int xDC, int yDC, _Graphics_widechar *lc,
 		iam (GraphicsScreen);
 		#if cairo
 			if (my duringXor) {
-			} else {
-				if (! my d_cairoGraphicsContext) return;
-				// TODO!
+				#if ALLOW_GDK_DRAWING
+					static GdkFont *font = nullptr;
+					if (! font) {
+						font = gdk_font_load ("-*-courier-medium-r-normal--*-120-*-*-*-*-iso8859-1");
+						if (! font) {
+							font = gdk_font_load ("-*-courier 10 pitch-medium-r-normal--*-120-*-*-*-*-iso8859-1");
+						}
+					}
+					if (font) {
+						gdk_draw_text_wc (my d_window, font, my d_gdkGraphicsContext, xDC, yDC, (const GdkWChar *) codes, nchars);
+					}
+					gdk_flush ();
+				#endif
+				return;
 			}
+			if (! my d_cairoGraphicsContext) return;
+			// TODO!
+			if (lc -> link) _Graphics_setColour (me, Graphics_BLUE);
 			int font = lc -> font.integer;
+			cairo_save (my d_cairoGraphicsContext);
+			cairo_translate (my d_cairoGraphicsContext, xDC, yDC);
+			//cairo_scale (my d_cairoGraphicsContext, 1, -1);
+			cairo_rotate (my d_cairoGraphicsContext, - my textRotation * NUMpi / 180.0);
+			const char *codes8 = Melder_peek32to8 (codes);
+			PangoFontDescription *font_description = PangoFontDescription_create (font, lc);
+			PangoLayout *layout = pango_cairo_create_layout (my d_cairoGraphicsContext);
+			pango_layout_set_font_description (layout, font_description);
+			pango_layout_set_text (layout, codes8, -1);
+			cairo_move_to (my d_cairoGraphicsContext, 0 /*xDC*/, 0 /*yDC*/);
+			// instead of pango_cairo_show_layout we use pango_cairo_show_layout_line to
+			// get the same text origin as cairo_show_text, i.e. baseline left, instead of Pango's top left!
+			pango_cairo_show_layout_line (my d_cairoGraphicsContext, pango_layout_get_line_readonly (layout, 0));
+			g_object_unref (layout);
+			cairo_restore (my d_cairoGraphicsContext);
+			if (lc -> link) _Graphics_setColour (me, my colour);
+			return;
+		#elif gdi
+			int font = lc -> font.integer;
+			WCHAR *codesW = Melder_peek32toW (codes);
+			if (my duringXor) {
+				int descent = (1.0/216) * my fontSize * my resolution;
+				int ascent = (1.0/72) * my fontSize * my resolution;
+				int maxWidth = 800, maxHeight = 200;
+				int baseline = 100, top = baseline - ascent - 1, bottom = baseline + descent + 1;
+				static int inited = 0;
+				static HDC dc;
+				static HBITMAP bitmap;
+				if (! inited) {
+					dc = CreateCompatibleDC (my d_gdiGraphicsContext);
+					bitmap = CreateCompatibleBitmap (my d_gdiGraphicsContext, maxWidth, maxHeight);
+					SelectBitmap (dc, bitmap);
+					SetBkMode (dc, TRANSPARENT);   // not the default!
+					SelectPen (dc, GetStockPen (BLACK_PEN));
+					SelectBrush (dc, GetStockBrush (BLACK_BRUSH));
+					SetTextAlign (dc, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
+					inited = 1;
+				}
+				width += 4;   // for slant
+				Rectangle (dc, 0, top, width, bottom);
+				SelectFont (dc, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
+				SetTextColor (dc, my d_winForegroundColour);
+				TextOutW (dc, 0, baseline, codesW, str16len ((const char16 *) codesW));
+				BitBlt (my d_gdiGraphicsContext, xDC, yDC - ascent, width, bottom - top, dc, 0, top, SRCINVERT);
+				return;
+			}
+			SelectPen (my d_gdiGraphicsContext, my d_winPen), SelectBrush (my d_gdiGraphicsContext, my d_winBrush);
+			if (lc -> link) SetTextColor (my d_gdiGraphicsContext, RGB (0, 0, 255)); else SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
+			SelectFont (my d_gdiGraphicsContext, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
+			if (my textRotation == 0.0) {
+				TextOutW (my d_gdiGraphicsContext, xDC, yDC, codesW, str16len ((const char16 *) codesW));
+			} else {
+				int restore = SaveDC (my d_gdiGraphicsContext);
+				SetGraphicsMode (my d_gdiGraphicsContext, GM_ADVANCED);
+				double a = my textRotation * NUMpi / 180.0, cosa = cos (a), sina = sin (a);
+				XFORM rotate = { (float) cosa, (float) - sina, (float) sina, (float) cosa, 0.0, 0.0 };
+				ModifyWorldTransform (my d_gdiGraphicsContext, & rotate, MWT_RIGHTMULTIPLY);
+				XFORM translate = { 1.0, 0.0, 0.0, 1.0, (float) xDC, (float) yDC };
+				ModifyWorldTransform (my d_gdiGraphicsContext, & translate, MWT_RIGHTMULTIPLY);
+				TextOutW (my d_gdiGraphicsContext, 0, 0, codesW, str16len ((const char16 *) codesW));
+				RestoreDC (my d_gdiGraphicsContext, restore);
+			}
+			if (lc -> link) SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
+			SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN)), SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
+			return;
 		#elif quartz
 			/*
 			 * Determine the font family.
@@ -736,234 +801,7 @@ static void charDraw (void *void_me, int xDC, int yDC, _Graphics_widechar *lc,
 				}
 			}
 			return;
-		#elif gdi
-			int font = lc -> font.integer;
 		#endif
-		/*
-		 * First handle the most common case: text without rotation.
-		 */
-		if (my textRotation == 0.0) {
-			/*
-			 * Unrotated text could be a link. If so, it will be blue.
-			 */
-			#if cairo
-				if (my duringXor) {
-				} else {
-					if (lc -> link) _Graphics_setColour (me, Graphics_BLUE);
-				}
-			#elif gdi
-			#endif
-			/*
-			 * The most common case: a native font.
-			 */
-			#if cairo
-				if (my duringXor) {
-					#if ALLOW_GDK_DRAWING
-						static GdkFont *font = nullptr;
-						if (! font) {
-							font = gdk_font_load ("-*-courier-medium-r-normal--*-120-*-*-*-*-iso8859-1");
-							if (! font) {
-								font = gdk_font_load ("-*-courier 10 pitch-medium-r-normal--*-120-*-*-*-*-iso8859-1");
-							}
-						}
-						if (font) {
-							gdk_draw_text_wc (my d_window, font, my d_gdkGraphicsContext, xDC, yDC, (const GdkWChar *) codes, nchars);
-						}
-						gdk_flush ();
-					#endif
-				} else {
-					Melder_assert (my d_cairoGraphicsContext);
-				#if USE_PANGO
-					PangoFontDescription *font_description = PangoFontDescription_create (font, lc);
-					PangoLayout *layout = pango_cairo_create_layout (my d_cairoGraphicsContext);
-					pango_layout_set_font_description (layout, font_description);
-					pango_layout_set_text (layout, Melder_peek32to8 (codes), -1);
-					cairo_move_to (my d_cairoGraphicsContext, xDC, yDC);
-					// instead of pango_cairo_show_layout we use pango_cairo_show_layout_line to
-					// get the same text origin as cairo_show_text, i.e. baseline left, instead of Pango's top left!
-					pango_cairo_show_layout_line (my d_cairoGraphicsContext, pango_layout_get_line_readonly (layout, 0));
-
-					g_object_unref (layout);
-					pango_font_description_free (font_description);
-				#else
-					enum _cairo_font_slant slant   = (lc -> style & Graphics_ITALIC ? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL);
-					enum _cairo_font_weight weight = (lc -> style & Graphics_BOLD   ? CAIRO_FONT_WEIGHT_BOLD  : CAIRO_FONT_WEIGHT_NORMAL);
-					cairo_set_font_size (my d_cairoGraphicsContext, lc -> size);
-					switch (font) {
-						case kGraphics_font_HELVETICA: cairo_select_font_face (my d_cairoGraphicsContext, "Helvetica", slant, weight); break;
-						case kGraphics_font_TIMES:     cairo_select_font_face (my d_cairoGraphicsContext, "Times New Roman", slant, weight); break;
-						case kGraphics_font_COURIER:   cairo_select_font_face (my d_cairoGraphicsContext, "Courier", slant, weight); break;
-						case kGraphics_font_PALATINO:  cairo_select_font_face (my d_cairoGraphicsContext, "Palatino", slant, weight); break;
-						case kGraphics_font_SYMBOL:    cairo_select_font_face (my d_cairoGraphicsContext, "Symbol", slant, weight); break;
-						case kGraphics_font_IPATIMES:  cairo_select_font_face (my d_cairoGraphicsContext, "Doulos SIL", slant, weight); break;
-						case kGraphics_font_DINGBATS:  cairo_select_font_face (my d_cairoGraphicsContext, "Dingbats", slant, weight); break;
-						default:                       cairo_select_font_face (my d_cairoGraphicsContext, "Sans", slant, weight); break;
-					}
-					cairo_move_to (my d_cairoGraphicsContext, xDC, yDC);
-					cairo_show_text (my d_cairoGraphicsContext, Melder_peek32to8 (codes));
-				#endif
-				}
-			#elif gdi
-				if (my duringXor) {
-					int descent = (1.0/216) * my fontSize * my resolution;
-					int ascent = (1.0/72) * my fontSize * my resolution;
-					int maxWidth = 800, maxHeight = 200;
-					int baseline = 100, top = baseline - ascent - 1, bottom = baseline + descent + 1;
-					static int inited = 0;
-					static HDC dc;
-					static HBITMAP bitmap;
-					if (! inited) {
-						dc = CreateCompatibleDC (my d_gdiGraphicsContext);
-						bitmap = CreateCompatibleBitmap (my d_gdiGraphicsContext, maxWidth, maxHeight);
-						SelectBitmap (dc, bitmap);
-						SetBkMode (dc, TRANSPARENT);   // not the default!
-						SelectPen (dc, GetStockPen (BLACK_PEN));
-						SelectBrush (dc, GetStockBrush (BLACK_BRUSH));
-						SetTextAlign (dc, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
-						inited = 1;
-					}
-					width += 4;   // for slant
-					Rectangle (dc, 0, top, width, bottom);
-					SelectFont (dc, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
-					SetTextColor (dc, my d_winForegroundColour);
-					WCHAR *codesW = Melder_peek32toW (codes);
-					TextOutW (dc, 0, baseline, codesW, str16len ((const char16 *) codesW));
-					BitBlt (my d_gdiGraphicsContext, xDC, yDC - ascent, width, bottom - top, dc, 0, top, SRCINVERT);
-				} else {
-					SelectPen (my d_gdiGraphicsContext, my d_winPen), SelectBrush (my d_gdiGraphicsContext, my d_winBrush);
-					if (lc -> link) SetTextColor (my d_gdiGraphicsContext, RGB (0, 0, 255)); else SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
-					SelectFont (my d_gdiGraphicsContext, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
-					WCHAR *codesW = Melder_peek32toW (codes);
-					TextOutW (my d_gdiGraphicsContext, xDC, yDC, codesW, str16len ((const char16 *) codesW));
-					if (lc -> link) SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
-					SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN)), SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
-				}
-			#endif
-			/*
-			 * Back to normal colour.
-			 */
-
-			#if cairo
-				if (my duringXor) {
-				} else {
-					if (lc -> link) _Graphics_setColour (me, my colour);
-				}
-			#elif gdi
-			#endif
-		} else {
-			/*
-			 * Rotated text.
-			 */
-			#if cairo
-				#if USE_PANGO
-					cairo_save (my d_cairoGraphicsContext);
-					cairo_translate (my d_cairoGraphicsContext, xDC, yDC);
-					//cairo_scale (my d_cairoGraphicsContext, 1, -1);
-					cairo_rotate (my d_cairoGraphicsContext, - my textRotation * NUMpi / 180.0);
-					cairo_move_to (my d_cairoGraphicsContext, 0, 0);
-					
-					PangoFontDescription *font_description = PangoFontDescription_create (font, lc);
-					PangoLayout *layout = pango_cairo_create_layout (my d_cairoGraphicsContext);
-					pango_layout_set_font_description (layout, font_description);
-					pango_layout_set_text (layout, Melder_peek32to8 (codes), -1);
-					// instead of pango_cairo_show_layout we use pango_cairo_show_layout_line to
-					// get the same text origin as cairo_show_text, i.e. baseline left, instead of Pango's top left!
-					pango_cairo_show_layout_line (my d_cairoGraphicsContext, pango_layout_get_line_readonly (layout, 0));
-
-					g_object_unref (layout);
-					pango_font_description_free (font_description);
-					cairo_restore (my d_cairoGraphicsContext);
-				#else
-					Melder_assert (my d_cairoGraphicsContext);
-					enum _cairo_font_slant  slant  = (lc -> style & Graphics_ITALIC ? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL);
-					enum _cairo_font_weight weight = (lc -> style & Graphics_BOLD   ? CAIRO_FONT_WEIGHT_BOLD  : CAIRO_FONT_WEIGHT_NORMAL);
-					cairo_set_font_size (my d_cairoGraphicsContext, lc -> size);
-					switch (font) {
-						case kGraphics_font_HELVETICA: cairo_select_font_face (my d_cairoGraphicsContext, "Helvetica", slant, weight); break;
-						case kGraphics_font_TIMES:     cairo_select_font_face (my d_cairoGraphicsContext, "Times"    , slant, weight); break;
-						case kGraphics_font_COURIER:   cairo_select_font_face (my d_cairoGraphicsContext, "Courier"  , slant, weight); break;
-						case kGraphics_font_PALATINO:  cairo_select_font_face (my d_cairoGraphicsContext, "Palatino" , slant, weight); break;
-						case kGraphics_font_SYMBOL:    cairo_select_font_face (my d_cairoGraphicsContext, "Symbol"   , slant, weight); break;
-						case kGraphics_font_IPATIMES:  cairo_select_font_face (my d_cairoGraphicsContext, "IPA Times", slant, weight); break;
-						case kGraphics_font_DINGBATS:  cairo_select_font_face (my d_cairoGraphicsContext, "Dingbats" , slant, weight); break;
-						default:                       cairo_select_font_face (my d_cairoGraphicsContext, "Sans"     , slant, weight); break;
-					}
-					cairo_save (my d_cairoGraphicsContext);
-					cairo_translate (my d_cairoGraphicsContext, xDC, yDC);
-					//cairo_scale (my d_cairoGraphicsContext, 1, -1);
-					cairo_rotate (my d_cairoGraphicsContext, - my textRotation * NUMpi / 180.0);
-					cairo_move_to (my d_cairoGraphicsContext, 0, 0);
-					cairo_show_text (my d_cairoGraphicsContext, Melder_peek32to8 (codes));
-					cairo_restore (my d_cairoGraphicsContext);
-					return;
-				#endif
-			#elif gdi
-				if (1) {
-					SelectPen (my d_gdiGraphicsContext, my d_winPen), SelectBrush (my d_gdiGraphicsContext, my d_winBrush);
-					if (lc -> link) SetTextColor (my d_gdiGraphicsContext, RGB (0, 0, 255)); else SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
-					SelectFont (my d_gdiGraphicsContext, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
-					int restore = SaveDC (my d_gdiGraphicsContext);
-					SetGraphicsMode (my d_gdiGraphicsContext, GM_ADVANCED);
-					double a = my textRotation * NUMpi / 180.0, cosa = cos (a), sina = sin (a);
-					XFORM rotate = { (float) cosa, (float) - sina, (float) sina, (float) cosa, 0.0, 0.0 };
-					ModifyWorldTransform (my d_gdiGraphicsContext, & rotate, MWT_RIGHTMULTIPLY);
-					XFORM translate = { 1.0, 0.0, 0.0, 1.0, (float) xDC, (float) yDC };
-					ModifyWorldTransform (my d_gdiGraphicsContext, & translate, MWT_RIGHTMULTIPLY);
-					WCHAR *codesW = Melder_peek32toW (codes);
-					TextOutW (my d_gdiGraphicsContext, 0 /*xDC*/, 0 /*yDC*/, codesW, str16len ((const char16 *) codesW));
-					RestoreDC (my d_gdiGraphicsContext, restore);
-					if (lc -> link) SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
-					SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN)), SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
-					return;
-				}
-			#endif
-			int ascent = (1.0/72) * my fontSize * my resolution;
-			int descent = (1.0/216) * my fontSize * my resolution;
-			int ix, iy /*, baseline = 1 + ascent * 2*/;
-			double cosa, sina;
-			#if gdi
-				int maxWidth = 1000, maxHeight = 600;   // BUG: printer???
-				int baseline = maxHeight / 4, top = baseline - ascent - 1, bottom = baseline + descent + 1;
-				static int inited = 0;
-				static HDC dc;
-				static HBITMAP bitmap;
-				if (! inited) {
-					dc = CreateCompatibleDC (my d_gdiGraphicsContext);
-					bitmap = CreateBitmap (/*my d_gdiGraphicsContext,*/ maxWidth, maxHeight, 1, 1, nullptr);
-					SelectBitmap (dc, bitmap);
-					inited = 1;
-				}
-			#endif
-			width += 4;   // leave room for slant
-			#if gdi
-				SelectPen (dc, GetStockPen (WHITE_PEN));
-				SelectBrush (dc, GetStockBrush (WHITE_BRUSH));
-				SetTextAlign (dc, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
-				Rectangle (dc, 0, top, maxWidth, bottom + 1);
-				//Rectangle (dc, 0, 0, maxWidth, maxHeight);
-				SelectPen (dc, GetStockPen (BLACK_PEN));
-				SelectBrush (dc, GetStockBrush (NULL_BRUSH));
-				SelectFont (dc, fonts [my resolutionNumber] [font] [lc -> size] [lc -> style]);
-				WCHAR *codesW = Melder_peek32toW (codes);
-				TextOutW (dc, 0, baseline, codesW, str16len ((const char16 *) codesW));
-			#endif
-			if (my textRotation == 90.0) { cosa = 0.0; sina = 1.0; }
-			else if (my textRotation == 270.0) { cosa = 0.0; sina = -1.0; }
-			else { double a = my textRotation * NUMpi / 180.0; cosa = cos (a); sina = sin (a); }
-			for (ix = 0; ix < width; ix ++) {
-				double dx1 = ix;
-				#if gdi
-					for (iy = top; iy <= bottom; iy ++) {
-						if (GetPixel (dc, ix, iy) == RGB (0, 0, 0)) {   // black?
-							int dy1 = iy - baseline;   // translate, rotate, translate
-							int xp = xDC + (int) (cosa * dx1 + sina * dy1);
-							int yp = yDC - (int) (sina * dx1 - cosa * dy1);
-							SetPixel (my d_gdiGraphicsContext, xp, yp, my d_winForegroundColour);
-						}
-					}
-				#endif
-			}
-		}
 	}
 }
 
@@ -1011,7 +849,7 @@ static int numberOfLinks = 0;
 static Graphics_Link links [100];    // a maximum of 100 links per string
 
 static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEachCharacterSeparately) {
-	if (my postScript) {
+	if (my postScript || (cairo && my duringXor)) {
 		for (_Graphics_widechar *character = string; character -> kar > U'\t'; character ++)
 			charSize (me, character);
 	} else {
@@ -1019,8 +857,8 @@ static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEa
 	 * Measure the size of each character.
 	 */
 	_Graphics_widechar *character;
-	#if quartz || (cairo && USE_PANGO && 0)
-		#if cairo && USE_PANGO
+	#if quartz || cairo
+		#if cairo
 			if (! ((GraphicsScreen) me) -> d_cairoGraphicsContext) return;
 		#endif
 		int numberOfDiacritics = 0;
@@ -1028,8 +866,8 @@ static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEa
 			/*
 			 * Determine the font family.
 			 */
-			Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
-			int font = chooseFont (me, info, lc);
+			Longchar_Info info = lc -> karInfo;
+			int font = chooseFont (me, lc);
 			lc -> font.string = nullptr;   // this erases font.integer!
 
 			/*
@@ -1096,54 +934,31 @@ static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEa
 				(my textRotation != 0.0 && my screen && my resolution > 150))
 			{
 				charCodes [nchars] = U'\0';
-				#if cairo && USE_PANGO
+				#if cairo
 					const char *codes8 = Melder_peek32to8 (charCodes);
 					int length = strlen (codes8);
 					PangoFontDescription *fontDescription = PangoFontDescription_create (lc -> font.integer, lc);
 
-					PangoLayout *pangoLayout = pango_cairo_create_layout (((GraphicsScreen) me) -> d_cairoGraphicsContext);
-					pango_layout_set_font_description (pangoLayout, fontDescription);
-					pango_layout_set_text (pangoLayout, codes8, -1);
-				
-					PangoFontMap *pangoFontMap = pango_cairo_font_map_get_default ();
-					PangoContext *pangoContext = pango_font_map_create_context (pangoFontMap);
-
-					PangoAttribute *pangoAttribute = pango_attr_font_desc_new (fontDescription);
-					PangoAttrList *pangoAttrList = pango_attr_list_new ();
-					pango_attr_list_insert (pangoAttrList, pangoAttribute);   // list is owner of attribute
-					PangoAttrIterator *pangoAttrIterator = pango_attr_list_get_iterator (pangoAttrList);
-					GList *pangoList = pango_itemize (pangoContext, codes8, 0, length, pangoAttrList, pangoAttrIterator);
-					PangoAnalysis pangoAnalysis = ((PangoItem *) pangoList -> data) -> analysis;
-					PangoGlyphString *pangoGlyphString = pango_glyph_string_new ();
-					pango_shape (codes8, length, & pangoAnalysis, pangoGlyphString);
-
 					/*
-						The following attempts to compute the width of a longer glyph string both fail,
-						because neither `pango_glyph_string_get_width()` nor `pango_glyph_string_extents()`
-						handle font substitution correctly: they seem to compute the width solely on the
-						basis of the (perhaps substituted) font of the *first* glyph. In Praat you can
-						see this when drawing the string "fdfgasdf\as\as\ct\ctfgdsghj" or the string
-						"fdfgasdf\al\al\be\befgdsghj" with right alignment.
-
-						Hence our use of `charSize()` instead of `charSizes()`, despite `charSize`'s problems
-						with the widths of diacritics.
+						Measuring the width of a text with a homogeneous Praat font
+						should still allow for Pango's font substitution.
+						Low-level sequences such as `pango_itemize--pango_shape--pango_glyph_string_get_width`
+						or `pango_itemize--pango_shape--pango_font_map_load_font--pango_glyph_string_extents`
+						don't accomplish this: they seem to compute the width solely on the
+						basis of the (perhaps substituted) font of the *first* glyph. By contrast, a PangoLayout
+						performs font substitution when drawing with `pango_cairo_show_layout_line`,
+						and also when measuring the width with `pango_layout_get_extents`.
+						Fortunately, a PangoLayout is 1.5 to 2 times faster than the two low-level methods
+						(measured 20170527).
 					*/
-					#if 0
-						lc -> width = pango_glyph_string_get_width (pangoGlyphString) / PANGO_SCALE;
-					#else
-						PangoFont *pangoFont = pango_font_map_load_font (pangoFontMap, pangoContext, fontDescription);
-						PangoRectangle inkRect, logicalRect;
-						pango_glyph_string_extents (pangoGlyphString, pangoFont, & inkRect, & logicalRect);
-						lc -> width = logicalRect. width / PANGO_SCALE;
-					#endif
-					pango_glyph_string_free (pangoGlyphString);
-					g_list_free_full (pangoList, (GDestroyNotify) pango_item_free);
-					//g_list_free (pangoList);
-					pango_attr_iterator_destroy (pangoAttrIterator);
-					pango_attr_list_unref (pangoAttrList);
-					//pango_attribute_destroy (pangoAttribute);   // list is owner
-					g_object_unref (pangoContext);
-					//g_object_unref (pangoFontMap);   // from the Pango manual: "should not be freed"
+					PangoLayout *layout = pango_cairo_create_layout (((GraphicsScreen) me) -> d_cairoGraphicsContext);
+					pango_layout_set_font_description (layout, fontDescription);
+					pango_layout_set_text (layout, codes8, -1);
+					PangoRectangle inkRect, logicalRect;
+					pango_layout_get_extents (layout, & inkRect, & logicalRect);
+					lc -> width = logicalRect. width / PANGO_SCALE;
+					Melder_assert (logicalRect.x == 0);
+					g_object_unref (layout);
 				#elif quartz
 					const char16 *codes16 = Melder_peek32to16 (charCodes);
 					int64 length = str16len (codes16);
@@ -1178,7 +993,7 @@ static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEa
 					CFRelease (cfstring);
 					[s release];
 					CFRelease (path);
-					//Longchar_Info info = Longchar_getInfoFromNative (lc -> kar);
+					//Longchar_Info info = lc -> karInfo;
 					//bool isDiacritic = info -> ps.times == 0;
 					//lc -> width = isDiacritic ? 0.0 : frameSize.width * lc -> size / 100.0;
 					lc -> width = frameSize.width * lc -> size / 100.0;
@@ -1211,14 +1026,14 @@ static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEa
 		if ((character -> style & Graphics_ITALIC) != 0) {
 			_Graphics_widechar *nextCharacter = character + 1;
 			if (nextCharacter -> kar <= U'\t') {
-				character -> width += SLANT_CORRECTION / 72 * my fontSize * my resolution;
+				character -> width += SCREEN_SLANT_CORRECTION / 72 * my fontSize * my resolution;
 			} else if (((nextCharacter -> style & Graphics_ITALIC) == 0 && nextCharacter -> baseline >= character -> baseline)
 				|| (character -> baseline == 0 && nextCharacter -> baseline > 0))
 			{
 				if (nextCharacter -> kar == U'.' || nextCharacter -> kar == U',')
-					character -> width += SLANT_CORRECTION / 144 * my fontSize * my resolution;
+					character -> width += SCREEN_SLANT_CORRECTION / 144 * my fontSize * my resolution;
 				else
-					character -> width += SLANT_CORRECTION / 72 * my fontSize * my resolution;
+					character -> width += SCREEN_SLANT_CORRECTION / 72 * my fontSize * my resolution;
 			}
 		}
 	}
@@ -1312,16 +1127,23 @@ static void drawOneCell (Graphics me, int xDC, int yDC, _Graphics_widechar lc []
 					if (plc <= lastlc) break;   // hopeless situation: no spaces; get over it
 					lastlc = plc;
 					plc -> kar = U'\n';   // replace space with newline
-					#if quartz
-						_Graphics_widechar *next = plc + 1;
-						if (next->style != plc->style ||
-							next->baseline != plc->baseline || next->size != plc->size || next->link != plc->link ||
-							next->font.integer != plc->font.integer || next->font.string != plc->font.string ||
-							next->rightToLeft != plc->rightToLeft)
-						{
-							// nothing
-						} else {
-							next -> width -= 0.3 * my fontSize * my resolution / 72.0;   // subtract the width of one space
+					#if quartz || cairo
+						if (my screen) {
+							/*
+								This part is needed when using the non-`charSize()` variant of `charSizes()`,
+								because otherwise you'll see extra spaces
+								before the first font switch on each non-initial line.
+							*/
+							_Graphics_widechar *next = plc + 1;
+							if (next->style != plc->style ||
+								next->baseline != plc->baseline || next->size != plc->size || next->link != plc->link ||
+								next->font.integer != plc->font.integer || next->font.string != plc->font.string ||
+								next->rightToLeft != plc->rightToLeft)
+							{
+								// nothing
+							} else {
+								next -> width -= 0.25 * my fontSize * my resolution / 72.0;   // subtract the width of one space
+							}
 						}
 					#endif
 					x = xDC + dx + my secondIndent * my scaleX;
@@ -1591,9 +1413,11 @@ static void parseTextIntoCellsLinesRuns (Graphics me, const char32 *txt /* catta
 		if (kar == U'/') {
 			out -> baseline -= out -> size / 12;
 			out -> size += out -> size / 10;
+			if (my screen) out -> font.integer = kGraphics_font_PALATINO;
 		}
 		out -> code = U'?';   // does this have any meaning?
 		out -> kar = kar;
+		out -> karInfo = Longchar_getInfoFromNative (kar);
 		out -> rightToLeft =
 			(kar >= 0x0590 && kar <= 0x06FF) ||
 			(kar >= 0xFE70 && kar <= 0xFEFF) ||
@@ -1602,16 +1426,16 @@ static void parseTextIntoCellsLinesRuns (Graphics me, const char32 *txt /* catta
 		out ++;
 	}
 	out -> kar = U'\0';   // end of text
+	out -> karInfo = Longchar_getInfoFromNative (kar);
 	out -> rightToLeft = false;
 }
 
 double Graphics_textWidth (Graphics me, const char32 *txt) {
-	double width;
 	if (! initBuffer (txt)) return 0.0;
 	initText (me);
 	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
 	charSizes (me, theWidechar, false);
-	width = textWidth (theWidechar);
+	double width = textWidth (theWidechar);
 	exitText (me);
 	return width / my scaleX;
 }
@@ -1778,13 +1602,12 @@ double Graphics_inqTextY (Graphics me) { return my textY; }
 int Graphics_getLinks (Graphics_Link **plinks) { *plinks = & links [0]; return numberOfLinks; }
 
 static double psTextWidth (_Graphics_widechar string [], bool useSilipaPS) {
-	_Graphics_widechar *character;
 	/*
 	 * The following has to be kept IN SYNC with GraphicsPostscript::charSize.
 	 */
 	double textWidth = 0;
-	for (character = string; character -> kar > U'\t'; character ++) {
-		Longchar_Info info = Longchar_getInfoFromNative (character -> kar);
+	for (_Graphics_widechar *character = & string [0]; character -> kar > U'\t'; character ++) {
+		Longchar_Info info = character -> karInfo;
 		int font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
 				info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
 				info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : character -> font.integer;
@@ -1826,7 +1649,7 @@ static double psTextWidth (_Graphics_widechar string [], bool useSilipaPS) {
 	/*
 	 * The following has to be kept IN SYNC with charSizes ().
 	 */
-	for (character = string; character -> kar > U'\t'; character ++) {
+	for (_Graphics_widechar *character = & string [0]; character -> kar > U'\t'; character ++) {
 		if ((character -> style & Graphics_ITALIC) != 0) {
 			_Graphics_widechar *nextCharacter = character + 1;
 			if (nextCharacter -> kar <= '\t') {
@@ -1877,52 +1700,47 @@ double Graphics_textWidth_ps (Graphics me, const char32 *txt, bool useSilipaPS)
 #endif
 
 #if cairo
-	#if USE_PANGO
-		static const char *testFont (const char *fontName) {
-			PangoFontMap *pangoFontMap = pango_cairo_font_map_get_default ();
-			PangoContext *pangoContext = pango_font_map_create_context (pangoFontMap);
-			PangoFontDescription *pangoFontDescription, *pangoFontDescription2;
-			PangoFont *pangoFont;
-			pangoFontDescription = pango_font_description_from_string (fontName);
-			pangoFont = pango_font_map_load_font (pangoFontMap, pangoContext, pangoFontDescription);
-			pangoFontDescription2 = pango_font_describe (pangoFont);
-			return pango_font_description_get_family (pangoFontDescription2);
-		}
-	#endif
+	static const char *testFont (const char *fontName) {
+		PangoFontDescription *pangoFontDescription, *pangoFontDescription2;
+		PangoFont *pangoFont;
+		pangoFontDescription = pango_font_description_from_string (fontName);
+		pangoFont = pango_font_map_load_font (thePangoFontMap, thePangoContext, pangoFontDescription);
+		pangoFontDescription2 = pango_font_describe (pangoFont);
+		return pango_font_description_get_family (pangoFontDescription2);
+	}
 	bool _GraphicsLin_tryToInitializeFonts () {
 		static bool inited = false;
 		if (inited) return true;
-		#if USE_PANGO
-			#if 0   /* For debugging: list all fonts. */
-				PangoFontMap *pangoFontMap = pango_cairo_font_map_get_default ();
-				PangoFontFamily **families;
-				int numberOfFamilies;
-				pango_font_map_list_families (pangoFontMap, & families, & numberOfFamilies);
-				for (int i = 0; i < numberOfFamilies; i ++) {
-					fprintf (stderr, "%d %s\n", i, pango_font_family_get_name (families [i]));
-				}
-				g_free (families);
-			#endif
-			const char *trueName;
-			trueName = testFont ("Times");
-			hasTimes = !! strstr (trueName, "Times") || !! strstr (trueName, "Roman") || !! strstr (trueName, "Serif");
-			trueName = testFont ("Helvetica");
-			hasHelvetica = !! strstr (trueName, "Helvetica") || !! strstr (trueName, "Arial") || !! strstr (trueName, "Sans");
-			trueName = testFont ("Courier");
-			hasCourier = !! strstr (trueName, "Courier") || !! strstr (trueName, "Mono");
-			trueName = testFont ("Palatino");
-			hasPalatino = !! strstr (trueName, "Palatino") || !! strstr (trueName, "Palladio");
-			trueName = testFont ("Doulos SIL");
-			hasDoulos = !! strstr (trueName, "Doulos");
-			trueName = testFont ("Charis SIL");
-			hasCharis = !! strstr (trueName, "Charis");
-			hasIpaSerif = hasDoulos || hasCharis;
-			testFont ("Symbol");
-			testFont ("Dingbats");
-			#if 0   /* For debugging: list font availability. */
-				fprintf (stderr, "times %d helvetica %d courier %d palatino %d doulos %d charis %d\n",
-					hasTimes, hasHelvetica, hasCourier, hasPalatino, hasDoulos, hasCharis);
-			#endif
+		thePangoFontMap = pango_cairo_font_map_get_default ();
+		thePangoContext = pango_font_map_create_context (thePangoFontMap);
+		#if 0   /* For debugging: list all fonts. */
+			PangoFontFamily **families;
+			int numberOfFamilies;
+			pango_font_map_list_families (thePangoFontMap, & families, & numberOfFamilies);
+			for (int i = 0; i < numberOfFamilies; i ++) {
+				fprintf (stderr, "%d %s\n", i, pango_font_family_get_name (families [i]));
+			}
+			g_free (families);
+		#endif
+		const char *trueName;
+		trueName = testFont ("Times");
+		hasTimes = !! strstr (trueName, "Times") || !! strstr (trueName, "Roman") || !! strstr (trueName, "Serif");
+		trueName = testFont ("Helvetica");
+		hasHelvetica = !! strstr (trueName, "Helvetica") || !! strstr (trueName, "Arial") || !! strstr (trueName, "Sans");
+		trueName = testFont ("Courier");
+		hasCourier = !! strstr (trueName, "Courier") || !! strstr (trueName, "Mono");
+		trueName = testFont ("Palatino");
+		hasPalatino = !! strstr (trueName, "Palatino") || !! strstr (trueName, "Palladio");
+		trueName = testFont ("Doulos SIL");
+		hasDoulos = !! strstr (trueName, "Doulos");
+		trueName = testFont ("Charis SIL");
+		hasCharis = !! strstr (trueName, "Charis");
+		hasIpaSerif = hasDoulos || hasCharis;
+		testFont ("Symbol");
+		testFont ("Dingbats");
+		#if 0   /* For debugging: list font availability. */
+			fprintf (stderr, "times %d helvetica %d courier %d palatino %d doulos %d charis %d\n",
+				hasTimes, hasHelvetica, hasCourier, hasPalatino, hasDoulos, hasCharis);
 		#endif
 		inited = true;
 		return true;
diff --git a/sys/Gui.h b/sys/Gui.h
index 5cb6457..93eea2b 100644
--- a/sys/Gui.h
+++ b/sys/Gui.h
@@ -19,9 +19,13 @@
  */
 
 /*
- * Determine the widget set.
+	Determine the widget set, honouring the compiler switch NO_GUI
+	and/or the compiler switch NO_GRAPHICS, which entails NO_GUI.
  */
-#if defined (NO_GRAPHICS)
+#if defined (NO_GRAPHICS) && ! defined (NO_GUI)
+	#define NO_GUI
+#endif
+#if defined (NO_GUI)
 	#define gtk 0
 	#define motif 0
 	#define cocoa 0
@@ -37,14 +41,27 @@
 	#define gtk 0
 	#define motif 0
 	#define cocoa 1
+#else
+	/*
+		Unknown platforms have no GUI.
+	*/
+	#define gtk 0
+	#define motif 0
+	#define cocoa 0
 #endif
 
 #include "Collection.h"
 
-#if gtk
-	#include <gtk/gtk.h>
-	#include <gdk/gdk.h>
-	#include <cairo/cairo.h>
+#if defined (UNIX)
+	#if gtk
+		#include <gtk/gtk.h>
+		#include <gdk/gdk.h>
+	#endif
+	#if ! defined (NO_GRAPHICS)
+		#include <cairo/cairo.h>
+		#include <pango/pango.h>
+		#include <pango/pangocairo.h>
+	#endif
 #elif defined (macintosh)
 	#include "macport_on.h"
     #include <Cocoa/Cocoa.h>
diff --git a/sys/Interpreter.cpp b/sys/Interpreter.cpp
index fd05c8a..153b8a5 100644
--- a/sys/Interpreter.cpp
+++ b/sys/Interpreter.cpp
@@ -1,6 +1,6 @@
 /* Interpreter.cpp
  *
- * Copyright (C) 1993-2011,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1993-2011,2013,2014,2015,2016,2017 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
@@ -1597,7 +1597,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 								while (Melder_isblank (*p)) p ++;   // go to first token after assignment
 								if (*p == U'\0')
 									Melder_throw (U"Missing right-hand expression in assignment to matrix ", matrixName.string, U".");
-								struct Formula_NumericMatrix value;
+								nummat value;
 								Interpreter_numericMatrixExpression (me, p, & value);
 								InterpreterVariable var = Interpreter_lookUpVariable (me, matrixName.string);
 								NUMmatrix_free (var -> numericMatrixValue. data, 1, 1);
@@ -1679,14 +1679,14 @@ void Interpreter_run (Interpreter me, char32 *text) {
 									Melder_throw (U"Matrix ", matrixName.string, U" does not exist.");
 								if (rowNumber < 1)
 									Melder_throw (U"A row number cannot be less than 1 (the row number you supplied is ", rowNumber, U").");
-								if (rowNumber > var -> numericMatrixValue. numberOfRows)
+								if (rowNumber > var -> numericMatrixValue. nrow)
 									Melder_throw (U"A row number cannot be greater than the number of rows (here ",
-										var -> numericMatrixValue. numberOfRows, U"). The row number you supplied is ", rowNumber, U".");
+										var -> numericMatrixValue. nrow, U"). The row number you supplied is ", rowNumber, U".");
 								if (columnNumber < 1)
 									Melder_throw (U"A column number cannot be less than 1 (the column number you supplied is ", columnNumber, U").");
-								if (columnNumber > var -> numericMatrixValue. numberOfColumns)
+								if (columnNumber > var -> numericMatrixValue. ncol)
 									Melder_throw (U"A column number cannot be greater than the number of columns (here ",
-										var -> numericMatrixValue. numberOfColumns, U"). The column number you supplied is ", columnNumber, U".");
+										var -> numericMatrixValue. ncol, U"). The column number you supplied is ", columnNumber, U".");
 								var -> numericMatrixValue. data [rowNumber] [columnNumber] = value;
 							} else Melder_throw (U"Missing '=' after matrix variable ", matrixName.string, U".");
 						} else {
@@ -1707,7 +1707,7 @@ void Interpreter_run (Interpreter me, char32 *text) {
 								while (Melder_isblank (*p)) p ++;   // go to first token after assignment
 								if (*p == U'\0')
 									Melder_throw (U"Missing right-hand expression in assignment to vector ", vectorName.string, U".");
-								struct Formula_NumericVector value;
+								numvec value;
 								Interpreter_numericVectorExpression (me, p, & value);
 								InterpreterVariable var = Interpreter_lookUpVariable (me, vectorName.string);
 								NUMvector_free (var -> numericVectorValue. data, 1);
@@ -1757,9 +1757,9 @@ void Interpreter_run (Interpreter me, char32 *text) {
 									Melder_throw (U"Vector ", vectorName.string, U" does not exist.");
 								if (indexValue < 1)
 									Melder_throw (U"A vector index cannot be less than 1 (the index you supplied is ", indexValue, U").");
-								if (indexValue > var -> numericVectorValue. numberOfElements)
+								if (indexValue > var -> numericVectorValue. size)
 									Melder_throw (U"A vector index cannot be greater than the number of elements (here ",
-										var -> numericVectorValue. numberOfElements, U"). The index you supplied is ", indexValue, U".");
+										var -> numericVectorValue. size, U"). The index you supplied is ", indexValue, U".");
 								var -> numericVectorValue. data [indexValue] = value;
 							} else Melder_throw (U"Missing '=' after vector variable ", vectorName.string, U".");
 						}
@@ -1989,14 +1989,14 @@ void Interpreter_numericExpression (Interpreter me, const char32 *expression, do
 	}
 }
 
-void Interpreter_numericVectorExpression (Interpreter me, const char32 *expression, struct Formula_NumericVector *value) {
+void Interpreter_numericVectorExpression (Interpreter me, const char32 *expression, numvec *value) {
 	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR, false);
 	struct Formula_Result result;
 	Formula_run (0, 0, & result);
 	*value = result. result.numericVectorResult;
 }
 
-void Interpreter_numericMatrixExpression (Interpreter me, const char32 *expression, struct Formula_NumericMatrix *value) {
+void Interpreter_numericMatrixExpression (Interpreter me, const char32 *expression, nummat *value) {
 	Formula_compile (me, nullptr, expression, kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX, false);
 	struct Formula_Result result;
 	Formula_run (0, 0, & result);
diff --git a/sys/Interpreter.h b/sys/Interpreter.h
index d4922aa..91aa497 100644
--- a/sys/Interpreter.h
+++ b/sys/Interpreter.h
@@ -2,7 +2,7 @@
 #define _Interpreter_h_
 /* Interpreter.h
  *
- * Copyright (C) 1993-2011,2013,2014,2015,2016 Paul Boersma
+ * Copyright (C) 1993-2011,2013,2014,2015,2016,2017 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,8 +28,8 @@
 Thing_define (InterpreterVariable, SimpleString) {
 	char32 *stringValue;
 	double numericValue;
-	struct Formula_NumericVector numericVectorValue;
-	struct Formula_NumericMatrix numericMatrixValue;
+	numvec numericVectorValue;
+	nummat numericMatrixValue;
 
 	void v_destroy () noexcept
 		override;
@@ -79,8 +79,8 @@ void Interpreter_stop (Interpreter me);   // can be called from any procedure ca
 
 void Interpreter_voidExpression (Interpreter me, const char32 *expression);
 void Interpreter_numericExpression (Interpreter me, const char32 *expression, double *value);
-void Interpreter_numericVectorExpression (Interpreter me, const char32 *expression, struct Formula_NumericVector *value);
-void Interpreter_numericMatrixExpression (Interpreter me, const char32 *expression, struct Formula_NumericMatrix *value);
+void Interpreter_numericVectorExpression (Interpreter me, const char32 *expression, numvec *value);
+void Interpreter_numericMatrixExpression (Interpreter me, const char32 *expression, nummat *value);
 void Interpreter_stringExpression (Interpreter me, const char32 *expression, char32 **value);
 void Interpreter_anyExpression (Interpreter me, const char32 *expression, struct Formula_Result *result);
 
diff --git a/sys/Makefile b/sys/Makefile
index e633149..de4be0b 100644
--- a/sys/Makefile
+++ b/sys/Makefile
@@ -1,5 +1,5 @@
 # Makefile of the library "sys"
-# Paul Boersma, 22 October 2016
+# Paul Boersma, 22 July 2017
 
 include ../makefile.defs
 
diff --git a/sys/MelderThread.h b/sys/MelderThread.h
index f018b5c..0d54356 100644
--- a/sys/MelderThread.h
+++ b/sys/MelderThread.h
@@ -2,7 +2,7 @@
 #define _MelderThread_h_
 /* MelderThread.h
  *
- * Copyright (C) 2014,2016 Paul Boersma
+ * Copyright (C) 2014,2016,2017 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
@@ -79,7 +79,7 @@
 	#define MelderThread_UNLOCK(_mutex)  _mutex = 0
 #endif
 
-static int MelderThread_getNumberOfProcessors () {
+inline static int MelderThread_getNumberOfProcessors () {
 	#if USE_WINTHREADS
 		return 8;
 	#elif USE_PTHREADS
diff --git a/sys/Picture.cpp b/sys/Picture.cpp
index 98b8f1c..f8bedc6 100644
--- a/sys/Picture.cpp
+++ b/sys/Picture.cpp
@@ -235,7 +235,7 @@ void Picture_setMouseSelectsInnerViewport (Picture me, int mouseSelectsInnerView
 }
 
 void structPicture :: v_destroy () noexcept {
-	Picture_erase (this);
+	//Picture_erase (this);   // dangerous if called from automatic destructor
 	Picture_Parent :: v_destroy ();
 }
 
diff --git a/sys/Tensor.h b/sys/Tensor.h
new file mode 100644
index 0000000..bbf3e36
--- /dev/null
+++ b/sys/Tensor.h
@@ -0,0 +1,32 @@
+#ifndef _tensor_h_
+#define _tensor_h_
+/* tensor.h
+ *
+ * Copyright (C) 2017 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this work. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+struct numvec {
+	long size;
+	double *data;
+};
+
+struct nummat {
+	long nrow, ncol;
+	double **data;
+};
+
+/* End of file tensor.h */
+#endif
diff --git a/sys/melder.h b/sys/melder.h
index d050ce4..c70efa7 100644
--- a/sys/melder.h
+++ b/sys/melder.h
@@ -187,7 +187,7 @@ inline static long a32tol (const char32 *string) {
 /*
  * Operating system version control.
  */
-#define ALLOW_GDK_DRAWING  1
+#define ALLOW_GDK_DRAWING  (gtk && 1)   /* change to (gtk && 0) if you want to try out GTK 3 */
 /* */
 
 typedef struct { double red, green, blue, transparency; } double_rgbt;
diff --git a/sys/melder_audio.cpp b/sys/melder_audio.cpp
index b52bed7..3f1910a 100644
--- a/sys/melder_audio.cpp
+++ b/sys/melder_audio.cpp
@@ -1335,7 +1335,7 @@ void MelderAudio_play16 (int16_t *buffer, long sampleRate, long numberOfSamples,
 						my samplesPlayed = my numberOfSamples;
 					}
 				} else /* my asynchronicity == kMelder_asynchronicityLevel_ASYNCHRONOUS */ {
-					#ifndef NO_GRAPHICS
+					#ifndef NO_GUI
 						my workProcId_gtk = g_idle_add (workProc_gtk, nullptr);
 					#endif
 					return;
diff --git a/sys/melder_debug.cpp b/sys/melder_debug.cpp
index 9482ce3..4a6dec8 100644
--- a/sys/melder_debug.cpp
+++ b/sys/melder_debug.cpp
@@ -1,6 +1,6 @@
 /* melder_debug.cpp
  *
- * Copyright (C) 2000-2012,2014,2015,2016 Paul Boersma
+ * Copyright (C) 2000-2012,2014,2015,2016,2017 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);
 }
 
-#if defined (linux) && ! defined (NO_GRAPHICS)
+#if defined (linux) && ! defined (NO_GUI)
 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;
-	#if defined (linux) && ! defined (NO_GRAPHICS)
+	#if defined (linux) && ! defined (NO_GUI)
 		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/oo.h b/sys/oo.h
index 9449248..cbc2e32 100644
--- a/sys/oo.h
+++ b/sys/oo.h
@@ -2,7 +2,7 @@
 #define _oo_h_
 /* oo.h
  *
- * Copyright (C) 1994-2012,2013,2015 Paul Boersma
+ * Copyright (C) 1994-2012,2013,2015,2017 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
@@ -24,7 +24,7 @@
 /*    i1: store as signed big-endian integer in 1 byte (-128..127). */
 /*    i2: store as signed big-endian integer in 2 bytes (-32768..32767). */
 /*    i4: store as signed big-endian integer in 4 bytes. */
-/*    u1: store as unsigned big-endian integer integer in 1 byte (0..255). */
+/*    u1: store as unsigned big-endian integer in 1 byte (0..255). */
 /*    u2: store as unsigned big-endian integer in 2 bytes (0..65535). */
 /*    u4: store as unsigned big-endian integer in 4 bytes. */
 /*    i1LE ... u4LE: store as little-endian integers. */
diff --git a/sys/praat.cpp b/sys/praat.cpp
index ce6bfab..3d2ace5 100644
--- a/sys/praat.cpp
+++ b/sys/praat.cpp
@@ -1229,9 +1229,9 @@ void praat_init (const char32 *title, int argc, char **argv)
 		Melder_tracingToFile (& tracingFile);
 	}
 
-	#if defined (NO_GRAPHICS)
+	#if defined (NO_GUI)
 		if (! Melder_batch) {
-			fprintf (stderr, "The barren edition of Praat cannot be used interactively. "
+			fprintf (stderr, "A no-GUI edition of Praat cannot be used interactively. "
 				"Supply \"--run\" and a script file name on the command line.\n");
 			exit (1);
 		}
@@ -1349,7 +1349,7 @@ void praat_init (const char32 *title, int argc, char **argv)
 		trace (U"showing the Objects window");
 		GuiThing_show (raam);
 	//Melder_fatal (U"stop");
-		#if defined (UNIX) && ! defined (NO_GRAPHICS)
+		#if defined (UNIX) && ! defined (NO_GUI)
 			try {
 				autofile f = Melder_fopen (& pidFile, "a");
 				#if ALLOW_GDK_DRAWING
diff --git a/sys/praat.h b/sys/praat.h
index 5f935ee..7bb7946 100644
--- a/sys/praat.h
+++ b/sys/praat.h
@@ -492,11 +492,22 @@ void praat_name2 (char32 *name, ClassInfo klas1, ClassInfo klas2);
 	klas me = nullptr; \
 	LOOP { if (CLASS == class##klas || Thing_isSubclass (CLASS, class##klas)) me = (klas) OBJECT; break; }
 
+#define FIND_ONE_WITH_IOBJECT(klas)  \
+	klas me = nullptr; int _klas_position = 0; \
+	LOOP { if (CLASS == class##klas) me = (klas) OBJECT, _klas_position = IOBJECT; break; } \
+	IOBJECT = _klas_position;
+
 #define FIND_TWO(klas1,klas2)  \
 	klas1 me = nullptr; klas2 you = nullptr; \
 	LOOP { if (CLASS == class##klas1) me = (klas1) OBJECT; else if (CLASS == class##klas2) you = (klas2) OBJECT; \
 	if (me && you) break; }
 
+#define FIND_TWO_WITH_IOBJECT(klas1,klas2)  \
+	klas1 me = nullptr; klas2 you = nullptr; int _klas1_position = 0; \
+	LOOP { if (CLASS == class##klas1) me = (klas1) OBJECT, _klas1_position = IOBJECT; \
+		else if (CLASS == class##klas2) you = (klas2) OBJECT; if (me && you) break; } \
+	IOBJECT = _klas1_position;
+
 #define FIND_COUPLE(klas)  \
 	klas me = nullptr, you = nullptr; \
 	LOOP if (CLASS == class##klas || Thing_isSubclass (CLASS, class##klas)) (me ? you : me) = (klas) OBJECT;
diff --git a/sys/praat_version.h b/sys/praat_version.h
index c637eff..d056e99 100644
--- a/sys/praat_version.h
+++ b/sys/praat_version.h
@@ -1,5 +1,5 @@
-#define PRAAT_VERSION_STR 6.0.29
-#define PRAAT_VERSION_NUM 6029
+#define PRAAT_VERSION_STR 6.0.30
+#define PRAAT_VERSION_NUM 6030
 #define PRAAT_YEAR 2017
-#define PRAAT_MONTH May
-#define PRAAT_DAY 24
+#define PRAAT_MONTH July
+#define PRAAT_DAY 22
diff --git a/sys/sendpraat.c b/sys/sendpraat.c
index e9fb8f3..3cd7540 100644
--- a/sys/sendpraat.c
+++ b/sys/sendpraat.c
@@ -1,6 +1,6 @@
 /* sendpraat.c */
 /* by Paul Boersma */
-/* 3 March 2017 */
+/* 20 July 2017 */
 
 /*
  * The sendpraat subroutine (Unix with GTK; Windows; Macintosh) sends a message
@@ -49,7 +49,7 @@
 	#include <unistd.h>
 	#include <ctype.h>
 	#include <wchar.h>
-	#if defined (NO_GRAPHICS)
+	#if defined (NO_GRAPHICS) || defined (NO_GUI)   /* for use inside Praat */
 		#define gtk 0
 	#else
 		#include <gtk/gtk.h>
@@ -234,7 +234,9 @@ char *sendpraat (void *display, const char *programName, long timeOut, const cha
 			 * Notify main window.
 			 */
 			GdkEventClient gevent;
+#if !GLIB_CHECK_VERSION(2,35,0)
 			g_type_init ();
+#endif
 			int displaySupplied = display != NULL;
 			if (! displaySupplied) {
 				display = gdk_display_open (getenv ("DISPLAY"));   /* GdkDisplay* */
@@ -458,7 +460,9 @@ wchar_t *sendpraatW (void *display, const wchar_t *programName, long timeOut, co
 			 */
 			GdkEventClient gevent;
 			int displaySupplied = display != NULL;
+#if !GLIB_CHECK_VERSION(2,35,0)
 			g_type_init ();
+#endif
 			if (! displaySupplied) {
 				display = gdk_display_open (getenv ("DISPLAY"));   /* GdkDisplay* */
 				if (display == NULL) {
diff --git a/test/script/vectors.praat b/test/script/vectors.praat
new file mode 100644
index 0000000..7ce56f4
--- /dev/null
+++ b/test/script/vectors.praat
@@ -0,0 +1,8 @@
+a# = { 1, 4, 9, 16, 25 }
+assert sum (a#) = 55
+assert mean (a#) = 11
+assert abs (mean (a#) - sum (a#) / 5) < 1e-14
+assert abs (stdev (a#) - 9.669539802906858) < 1e-14
+assert abs (stdev (a#) - sqrt (sumOver (i to 5, (a# [i] - mean (a#)) ^ 2) / 4)) < 1e-14
+assert abs (center (a#) - 4.090909090909091) < 1e-14
+assert abs (center (a#) - sumOver (i to 5, i * a# [i]) / sum (a#)) < 1e-14
diff --git a/test/sys/graphicsTextSpeed.praat b/test/sys/graphicsTextSpeed.praat
new file mode 100644
index 0000000..e29fdf3
--- /dev/null
+++ b/test/sys/graphicsTextSpeed.praat
@@ -0,0 +1,9 @@
+stopwatch
+for i to 1000
+	Text: 0.5, "centre", 0.5, "half", "fhsdgfsghjkh\as\as\ct\ctjghfgjhlhj"
+endfor
+writeInfoLine: stopwatch
+for i to 1000
+	Text: 0.5, "centre", 0.5, "half", ""
+endfor
+appendInfoLine: stopwatch
diff --git a/test/sys/object.praat b/test/sys/object.praat
new file mode 100644
index 0000000..f78c244
--- /dev/null
+++ b/test/sys/object.praat
@@ -0,0 +1,35 @@
+writeInfoLine: "object[]..."
+nsamp = 44100
+sound = Create Sound from formula: "sineWithNoise", 1, 0, 1, nsamp, "1/2 * sin(2*pi*377*x) + randomGauss(0,0.1)"
+niter = 1e3
+stopwatch
+
+procedure do (formula$)
+	for i to niter
+		Formula: formula$
+	endfor
+	appendInfoLine: fixed$ (1e9 * stopwatch / niter / nsamp, 1), " ns: ", formula$
+endproc
+
+ at do: "0"
+ at do: "5"
+ at do: "1/2 * sin(2*pi*377*x) + randomGauss(0,0.1)"
+ at do: "self + 5"
+ at do: "Sound_sineWithNoise[] + 5"
+ at do: "Object_'sound'[] + 5"
+ at do: "self"
+ at do: "self [col]"
+ at do: "self [col] + 5"
+ at do: "Sound_sineWithNoise [col] + 5"
+ at do: "Object_'sound' [col] + 5"
+ at do: "object [sound, col] + 5"
+ at do: "self"
+ at do: "Sound_sineWithNoise [row, col] + 5"
+ at do: "Object_'sound' [row, col] + 5"
+ at do: "self [row, col] + 5"
+ at do: "object [sound, row, col] + 5"
+ at do: "object [""Sound sineWithNoise"", row, col] + 5"
+name$ = "Sound sineWithNoise"
+ at do: "object [name$, row, col] + 5"
+appendInfoLine: "OK"
+Remove
\ 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