[med-svn] [praat] 01/03: Imported Upstream version 5.3.57+dfsg

Rafael Laboissière rlaboiss-guest at alioth.debian.org
Mon Oct 28 12:47:17 UTC 2013


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

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

commit c21c4f08fbe2d4718fd4b09571dd5a68f1249442
Author: Rafael Laboissiere <rafael at laboissiere.net>
Date:   Mon Oct 28 13:02:20 2013 +0100

    Imported Upstream version 5.3.57+dfsg
---
 fon/Makefile                          |    6 +-
 fon/Matrix.cpp                        |    2 +-
 fon/Matrix.h                          |    6 +-
 fon/Matrix_def.h                      |   14 +-
 fon/Photo.cpp                         |  314 ++++++++++++++++++++++++++++++++
 fon/Photo.h                           |   81 +++++++++
 fon/{Matrix_def.h => Photo_def.h}     |   71 +++++---
 fon/Sampled.h                         |    2 -
 fon/SampledXY.cpp                     |   64 +++++++
 fon/SampledXY.h                       |   62 +++++++
 fon/{Matrix_def.h => SampledXY_def.h} |   42 ++---
 fon/manual_tutorials.cpp              |    5 +-
 fon/praat_Fon.cpp                     |  319 ++++++++++++++++++++++++++++++++-
 main/praat_mac.plist                  |   38 +++-
 makefiles/makefile.defs.linux.alsa    |    4 +-
 makefiles/makefile.defs.linux.silent  |    4 +-
 num/NUM.h                             |    6 +-
 praat64_xcodeproj.dmg                 |  Bin 300908 -> 0 bytes
 praat_xcodeproj.dmg                   |  Bin 355256 -> 0 bytes
 sys/Formula.cpp                       |    5 -
 sys/Graphics.h                        |    4 +
 sys/GraphicsP.h                       |    4 +-
 sys/GraphicsScreen.cpp                |    1 +
 sys/Graphics_image.cpp                |  138 ++++++++++----
 sys/Graphics_record.cpp               |   34 +++-
 sys/GuiThing.cpp                      |    5 +
 sys/melder.h                          |    2 +
 sys/melder_audio.cpp                  |   77 ++++----
 sys/oo_DESCRIPTION.h                  |    3 +
 sys/oo_DESTROY.h                      |    2 +-
 sys/oo_READ_BINARY.h                  |   12 +-
 sys/oo_READ_TEXT.h                    |   12 +-
 sys/oo_WRITE_BINARY.h                 |   16 +-
 sys/oo_WRITE_TEXT.h                   |   15 +-
 sys/praat_version.h                   |    8 +-
 35 files changed, 1202 insertions(+), 176 deletions(-)

diff --git a/fon/Makefile b/fon/Makefile
index 74977ba..3643c00 100644
--- a/fon/Makefile
+++ b/fon/Makefile
@@ -1,12 +1,12 @@
 # Makefile of the library "fon"
-# Paul Boersma, 24 August 2013
+# Paul Boersma, 27 October 2013
 
 include ../makefile.defs
 
 CPPFLAGS = -I ../num -I ../kar -I ../sys -I ../dwsys -I ../stat -I ../dwtools -I ../LPC -I ../fon -I ../external/portaudio -I ../external/flac -I ../external/mp3
 
 OBJECTS = Transition.o Distributions_and_Transition.o \
-   Function.o Sampled.o Matrix.o Vector.o Polygon.o PointProcess.o \
+   Function.o Sampled.o SampledXY.o Matrix.o Vector.o Polygon.o PointProcess.o \
    Matrix_and_PointProcess.o Matrix_and_Polygon.o AnyTier.o RealTier.o \
    Sound.o LongSound.o Sound_files.o Sound_audio.o PointProcess_and_Sound.o Sound_PointProcess.o ParamCurve.o \
    Pitch.o Harmonicity.o Intensity.o Matrix_and_Pitch.o Sound_to_Pitch.o \
@@ -30,7 +30,7 @@ OBJECTS = Transition.o Distributions_and_Transition.o \
    WordList.o SpellingChecker.o \
    FujisakiPitch.o \
    ExperimentMFC.o RunnerMFC.o manual_Exp.o praat_Exp.o \
-   Movie.o MovieWindow.o \
+   Photo.o Movie.o MovieWindow.o \
    Corpus.o \
    manual_Picture.o manual_Manual.o manual_Script.o \
    manual_soundFiles.o manual_tutorials.o manual_references.o \
diff --git a/fon/Matrix.cpp b/fon/Matrix.cpp
index 676e2ee..5d51cec 100644
--- a/fon/Matrix.cpp
+++ b/fon/Matrix.cpp
@@ -39,7 +39,7 @@
 #include "oo_DESCRIPTION.h"
 #include "Matrix_def.h"
 
-Thing_implement (Matrix, Sampled, 2);
+Thing_implement (Matrix, SampledXY, 2);
 
 void structMatrix :: v_info () {
 	structData :: v_info ();
diff --git a/fon/Matrix.h b/fon/Matrix.h
index c5f5874..6907596 100644
--- a/fon/Matrix.h
+++ b/fon/Matrix.h
@@ -2,7 +2,7 @@
 #define _Matrix_h_
 /* Matrix.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,14 +19,14 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-#include "Sampled.h"
+#include "SampledXY.h"
 #include "Graphics.h"
 #include "Table.h"
 #include "TableOfReal.h"
 #include "Interpreter_decl.h"
 
 #include "Matrix_def.h"
-oo_CLASS_CREATE (Matrix, Sampled);
+oo_CLASS_CREATE (Matrix, SampledXY);
 
 void Matrix_init
 	(Matrix me, double xmin, double xmax, long nx, double dx, double x1,
diff --git a/fon/Matrix_def.h b/fon/Matrix_def.h
index 87d3408..273d5a3 100644
--- a/fon/Matrix_def.h
+++ b/fon/Matrix_def.h
@@ -1,6 +1,6 @@
 /* Matrix_def.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,13 +19,8 @@
 
 
 #define ooSTRUCT Matrix
-oo_DEFINE_CLASS (Matrix, Sampled)
+oo_DEFINE_CLASS (Matrix, SampledXY)
 
-	oo_DOUBLE (ymin)
-	oo_DOUBLE (ymax)
-	oo_LONG (ny)
-	oo_DOUBLE (dy)
-	oo_DOUBLE (y1)
 	#if oo_READING
 		if (Melder_debug == 45)
 			Melder_casual ("structMatrix :: read: Going to read %ld rows of %ld columns.", ny, nx);
@@ -43,11 +38,6 @@ oo_DEFINE_CLASS (Matrix, Sampled)
 			virtual void v_info ();
 			virtual bool v_hasGetNrow      () { return true; }   virtual double v_getNrow      ()        { return ny; }
 			virtual bool v_hasGetNcol      () { return true; }   virtual double v_getNcol      ()        { return nx; }
-			virtual bool v_hasGetYmin      () { return true; }   virtual double v_getYmin      ()        { return ymin; }
-			virtual bool v_hasGetYmax      () { return true; }   virtual double v_getYmax      ()        { return ymax; }
-			virtual bool v_hasGetNy        () { return true; }   virtual double v_getNy        ()        { return ny; }
-			virtual bool v_hasGetDy        () { return true; }   virtual double v_getDy        ()        { return dy; }
-			virtual bool v_hasGetY         () { return true; }   virtual double v_getY         (long iy) { return y1 + (iy - 1) * dy; }
 			virtual bool v_hasGetMatrix    () { return true; }   virtual double v_getMatrix    (long irow, long icol);
 			virtual bool v_hasGetFunction2 () { return true; }   virtual double v_getFunction2 (double x, double y);
 			virtual double v_getValueAtSample (long isamp, long ilevel, int unit);
diff --git a/fon/Photo.cpp b/fon/Photo.cpp
new file mode 100644
index 0000000..bb38587
--- /dev/null
+++ b/fon/Photo.cpp
@@ -0,0 +1,314 @@
+/* Photo.cpp
+ *
+ * Copyright (C) 2013 Paul Boersma
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "Photo.h"
+#include "NUM2.h"
+#include "Formula.h"
+#if defined (macintosh)
+	#include "macport_on.h"
+	#include <Cocoa/Cocoa.h>
+	#include "macport_off.h"
+#endif
+
+#include "oo_DESTROY.h"
+#include "Photo_def.h"
+#include "oo_COPY.h"
+#include "Photo_def.h"
+#include "oo_EQUAL.h"
+#include "Photo_def.h"
+#include "oo_CAN_WRITE_AS_ENCODING.h"
+#include "Photo_def.h"
+#include "oo_WRITE_TEXT.h"
+#include "Photo_def.h"
+#include "oo_READ_TEXT.h"
+#include "Photo_def.h"
+#include "oo_WRITE_BINARY.h"
+#include "Photo_def.h"
+#include "oo_READ_BINARY.h"
+#include "Photo_def.h"
+#include "oo_DESCRIPTION.h"
+#include "Photo_def.h"
+
+Thing_implement (Photo, SampledXY, 0);
+
+void structPhoto :: v_info () {
+	structData :: v_info ();
+	MelderInfo_writeLine (L"xmin: ", Melder_double (xmin));
+	MelderInfo_writeLine (L"xmax: ", Melder_double (xmax));
+	MelderInfo_writeLine (L"Number of columns: ", Melder_integer (nx));
+	MelderInfo_writeLine (L"dx: ", Melder_double (dx), L" (-> sampling rate ", Melder_double (1.0 / dx), L" )");
+	MelderInfo_writeLine (L"x1: ", Melder_double (x1));
+	MelderInfo_writeLine (L"ymin: ", Melder_double (ymin));
+	MelderInfo_writeLine (L"ymax: ", Melder_double (ymax));
+	MelderInfo_writeLine (L"Number of rows: ", Melder_integer (ny));
+	MelderInfo_writeLine (L"dy: ", Melder_double (dy), L" (-> sampling rate ", Melder_double (1.0 / dy), L" )");
+	MelderInfo_writeLine (L"y1: ", Melder_double (y1));
+}
+
+void structPhoto :: f_init
+	(double xmin, double xmax, long nx, double dx, double x1,
+	 double ymin, double ymax, long ny, double dy, double y1)
+{
+	structSampledXY :: f_init (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	this -> d_red =          Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	this -> d_green =        Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	this -> d_blue =         Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+	this -> d_transparency = Matrix_create (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+}
+
+Photo Photo_create
+	(double xmin, double xmax, long nx, double dx, double x1,
+	 double ymin, double ymax, long ny, double dy, double y1)
+{
+	try {
+		autoPhoto me = Thing_new (Photo);
+		my f_init (xmin, xmax, nx, dx, x1, ymin, ymax, ny, dy, y1);
+		return me.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Matrix object not created.");
+	}
+}
+
+Photo Photo_createSimple (long numberOfRows, long numberOfColumns) {
+	try {
+		autoPhoto me = Thing_new (Photo);
+		my f_init (0.5, numberOfColumns + 0.5, numberOfColumns, 1, 1,
+		           0.5, numberOfRows    + 0.5, numberOfRows,    1, 1);
+		return me.transfer();
+	} catch (MelderError) {
+		Melder_throw ("Matrix object not created.");
+	}
+}
+
+Photo Photo_readFromImageFile (MelderFile file) {
+	autoPhoto me = NULL;
+	#if defined (linux)
+		(void) file;
+		Melder_throw ("Cannot read image files on Linux yet. Try the Mac.");
+		// NYI
+	#elif defined (_WIN32)
+		(void) file;
+		Melder_throw ("Cannot read image files on Windows yet. Try the Mac.");
+		// NYI
+	#elif defined (macintosh)
+		char utf8 [500];
+		Melder_wcsTo8bitFileRepresentation_inline (file -> path, utf8);
+		CFStringRef path = CFStringCreateWithCString (NULL, utf8, kCFStringEncodingUTF8);
+		CFURLRef url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, false);
+		CFRelease (path);
+		CGImageSourceRef imageSource = CGImageSourceCreateWithURL (url, NULL);
+		CFRelease (url);
+		if (imageSource != NULL) {
+			CGImageRef image = CGImageSourceCreateImageAtIndex (imageSource, 0, NULL);
+			CFRelease (imageSource);
+			if (image != NULL) {
+				long width = CGImageGetWidth (image);
+				long height = CGImageGetHeight (image);
+				me.reset (Photo_createSimple (height, width));
+				long bitsPerPixel = CGImageGetBitsPerPixel (image);
+				long bitsPerComponent = CGImageGetBitsPerComponent (image);
+				long bytesPerRow = CGImageGetBytesPerRow (image);
+				trace ("%ld bits per pixel, %ld bits per component, %ld bytes per row", bitsPerPixel, bitsPerComponent, bytesPerRow);
+				/*
+				 * Now we probably need to use:
+				 * CGColorSpaceRef CGImageGetColorSpace (CGImageRef image);
+				 * CGImageAlphaInfo CGImageGetAlphaInfo (CGImageRef image);
+				 */
+				CGDataProviderRef dataProvider = CGImageGetDataProvider (image);   // not retained, so don't release
+				CFDataRef data = CGDataProviderCopyData (dataProvider);
+				uint8_t *pixelData = (uint8_t *) CFDataGetBytePtr (data);
+				for (long irow = 1; irow <= height; irow ++) {
+					uint8_t *rowAddress = pixelData + bytesPerRow * (height - irow);
+					for (long icol = 1; icol <= width; icol ++) {
+						my d_red -> z [irow] [icol] = (*rowAddress ++) / 255.0;
+						my d_green -> z [irow] [icol] = (*rowAddress ++) / 255.0;
+						my d_blue -> z [irow] [icol] = (*rowAddress ++) / 255.0;
+						my d_transparency -> z [irow] [icol] = 1.0 - (*rowAddress ++) / 255.0;
+					}
+				}
+				CFRelease (data);
+				CGImageRelease (image);
+			}
+		}
+	#endif
+	return me.transfer();
+}
+
+#if defined (macintosh)
+	#include <time.h>
+	#include "macport_on.h"
+	static void _mac_releaseDataCallback (void *info, const void *data, size_t size) {
+		(void) info;
+		(void) size;
+		Melder_free (data);
+	}
+#endif
+
+#ifdef macintosh
+void structPhoto :: _mac_saveAsImageFile (MelderFile file, const void *which) {
+		long bytesPerRow = this -> nx * 4;
+		long numberOfRows = this -> ny;
+		unsigned char *imageData = Melder_malloc_f (unsigned char, bytesPerRow * numberOfRows);
+		for (long irow = 1; irow <= ny; irow ++) {
+			uint8_t *rowAddress = imageData + bytesPerRow * (ny - irow);
+			for (long icol = 1; icol <= nx; icol ++) {
+				* rowAddress ++ = round (d_red          -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = round (d_green        -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = round (d_blue         -> z [irow] [icol] * 255.0);
+				* rowAddress ++ = 255 - round (d_transparency -> z [irow] [icol] * 255.0);
+			}
+		}
+		CGColorSpaceRef colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);   // used to be kCGColorSpaceUserRGB
+		Melder_assert (colourSpace != NULL);
+		CGDataProviderRef dataProvider = CGDataProviderCreateWithData (NULL,
+			imageData,
+			bytesPerRow * numberOfRows,
+			_mac_releaseDataCallback   // needed?
+		);
+		Melder_assert (dataProvider != NULL);
+		CGImageRef image = CGImageCreate (this -> nx, numberOfRows,
+			8, 32, bytesPerRow, colourSpace, kCGImageAlphaNone, dataProvider, NULL, false, kCGRenderingIntentDefault);
+		CGDataProviderRelease (dataProvider);
+		Melder_assert (image != NULL);
+		NSString *path = (NSString *) Melder_peekWcsToCfstring (Melder_fileToPath (file));
+		CFURLRef url = (CFURLRef) [NSURL   fileURLWithPath: path   isDirectory: NO];
+		CGImageDestinationRef destination = CGImageDestinationCreateWithURL (url, (CFStringRef) which, 1, NULL);
+		CGImageDestinationAddImage (destination, image, nil);
+
+		if (! CGImageDestinationFinalize (destination)) {
+			//Melder_throw;
+		}
+
+		CFRelease (destination);
+		CGColorSpaceRelease (colourSpace);
+		CGImageRelease (image);
+}
+#endif
+
+void structPhoto :: f_saveAsPNG (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypePNG);
+	#endif
+}
+
+void structPhoto :: f_saveAsTIFF (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeTIFF);
+	#endif
+}
+
+void structPhoto :: f_saveAsGIF (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeGIF);
+	#endif
+}
+
+void structPhoto :: f_saveAsWindowsBitmapFile (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeBMP);
+	#endif
+}
+
+void structPhoto :: f_saveAsJPEG (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeJPEG);
+	#endif
+}
+
+void structPhoto :: f_saveAsJPEG2000 (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeJPEG2000);
+	#endif
+}
+
+void structPhoto :: f_saveAsAppleIconFile (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeAppleICNS);
+	#endif
+}
+
+void structPhoto :: f_saveAsWindowsIconFile (MelderFile file) {
+	#ifdef macintosh
+		_mac_saveAsImageFile (file, kUTTypeICO);
+	#endif
+}
+
+void structPhoto :: f_replaceRed (Matrix red) {
+	autoMatrix copy = Data_copy (red);
+	forget (d_red);
+	d_red = copy.transfer();
+}
+
+void structPhoto :: f_replaceGreen (Matrix green) {
+	autoMatrix copy = Data_copy (green);
+	forget (d_green);
+	d_green = copy.transfer();
+}
+
+void structPhoto :: f_replaceBlue (Matrix blue) {
+	autoMatrix copy = Data_copy (blue);
+	forget (d_blue);
+	d_blue = copy.transfer();
+}
+
+void structPhoto :: f_replaceTransparency (Matrix transparency) {
+	autoMatrix copy = Data_copy (transparency);
+	forget (d_transparency);
+	d_transparency = copy.transfer();
+}
+
+static void cellArrayOrImage (Photo me, Graphics g, double xmin, double xmax, double ymin, double ymax, bool interpolate) {
+	if (xmax <= xmin) { xmin = my xmin; xmax = my xmax; }
+	if (ymax <= ymin) { ymin = my ymin; ymax = my ymax; }
+	long ixmin, ixmax, iymin, iymax;
+	Sampled_getWindowSamples (me, xmin - 0.49999 * my dx, xmax + 0.49999 * my dx, & ixmin, & ixmax);
+	my f_getWindowSamplesY       (ymin - 0.49999 * my dy, ymax + 0.49999 * my dy, & iymin, & iymax);
+	if (xmin >= xmax || ymin >= ymax) return;
+	Graphics_setInner (g);
+	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
+	autoNUMmatrix <double_rgbt> z (iymin, iymax, ixmin, ixmax);
+	for (long iy = iymin; iy <= iymax; iy ++) {
+		for (long ix = ixmin; ix <= ixmax; ix ++) {
+			z [iy] [ix]. red          = my d_red          -> z [iy] [ix];
+			z [iy] [ix]. green        = my d_green        -> z [iy] [ix];
+			z [iy] [ix]. blue         = my d_blue         -> z [iy] [ix];
+			z [iy] [ix]. transparency = my d_transparency -> z [iy] [ix];
+		}
+	}
+	if (interpolate)
+		Graphics_image_colour (g, z.peek(),
+			ixmin, ixmax, Matrix_columnToX (me, ixmin - 0.5), Matrix_columnToX (me, ixmax + 0.5),
+			iymin, iymax, Matrix_rowToY (me, iymin - 0.5), Matrix_rowToY (me, iymax + 0.5), 0.0, 1.0);
+	else
+		Graphics_cellArray_colour (g, z.peek(),
+			ixmin, ixmax, Matrix_columnToX (me, ixmin - 0.5), Matrix_columnToX (me, ixmax + 0.5),
+			iymin, iymax, Matrix_rowToY (me, iymin - 0.5), Matrix_rowToY (me, iymax + 0.5), 0.0, 1.0);
+	//Graphics_rectangle (g, xmin, xmax, ymin, ymax);
+	Graphics_unsetInner (g);
+}
+
+void structPhoto :: f_paintImage (Graphics g, double xmin, double xmax, double ymin, double ymax) {
+	cellArrayOrImage (this, g, xmin, xmax, ymin, ymax, true);
+}
+
+void structPhoto :: f_paintCells (Graphics g, double xmin, double xmax, double ymin, double ymax) {
+	cellArrayOrImage (this, g, xmin, xmax, ymin, ymax, false);
+}
+
+/* End of file Photo.cpp */
diff --git a/fon/Photo.h b/fon/Photo.h
new file mode 100644
index 0000000..f81f1a6
--- /dev/null
+++ b/fon/Photo.h
@@ -0,0 +1,81 @@
+#ifndef _Photo_h_
+#define _Photo_h_
+/* Photo.h
+ *
+ * Copyright (C) 2013 Paul Boersma
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "Matrix.h"
+
+#include "Photo_def.h"
+oo_CLASS_CREATE (Photo, SampledXY);
+
+
+Photo Photo_create
+	(double xmin, double xmax, long nx, double dx, double x1,
+	 double ymin, double ymax, long ny, double dy, double y1);
+/*
+	Function:
+		return a new opaque black Photo, or NULL if out of memory.
+	Preconditions:
+		xmin <= xmax;   ymin <= ymax;
+		nx >= 1;  ny >= 1;
+		dx > 0.0;   dy > 0.0;
+	Postconditions:
+		result -> xmin == xmin;
+		result -> xmax == xmax;
+		result -> ymin == ymin;
+		result -> ymax == ymax;
+		result -> nx == nx;
+		result -> ny == ny;
+		result -> dx == dx;
+		result -> dy == dy;
+		result -> x1 == x1;
+		result -> y1 == y1;
+		result -> d_red -> z [1..ny] [1..nx] == 0.0;
+		result -> d_green -> z [1..ny] [1..nx] == 0.0;
+		result -> d_blue -> z [1..ny] [1..nx] == 0.0;
+		result -> d_transparency -> z [1..ny] [1..nx] == 0.0;
+*/
+
+Photo Photo_createSimple (long numberOfRows, long numberOfColumns);
+/*
+	Function:
+		return a new opaque black Photo, or NULL if out of memory.
+	Preconditions:
+		numberOfRows >= 1;  numberOfColumns >= 1;
+	Postconditions:
+		result -> xmin == 0.5;
+		result -> xmax == numberOfColumns + 0.5;
+		result -> ymin == 0.5;
+		result -> ymax == numberOfRows + 0.5;
+		result -> nx == numberOfColumns;
+		result -> ny == numberOfRows;
+		result -> dx == 1;
+		result -> dy == 1;
+		result -> x1 == 1;
+		result -> y1 == 1;
+		result -> d_red -> z [1..ny] [1..nx] == 0.0;
+		result -> d_green -> z [1..ny] [1..nx] == 0.0;
+		result -> d_blue -> z [1..ny] [1..nx] == 0.0;
+		result -> d_transparency -> z [1..ny] [1..nx] == 0.0;
+*/
+
+Photo Photo_readFromImageFile (MelderFile file);
+
+/* End of file Photo.h */
+#endif
diff --git a/fon/Matrix_def.h b/fon/Photo_def.h
similarity index 50%
copy from fon/Matrix_def.h
copy to fon/Photo_def.h
index 87d3408..14e8217 100644
--- a/fon/Matrix_def.h
+++ b/fon/Photo_def.h
@@ -1,6 +1,6 @@
-/* Matrix_def.h
+/* Photo_def.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,25 +18,13 @@
  */
 
 
-#define ooSTRUCT Matrix
-oo_DEFINE_CLASS (Matrix, Sampled)
-
-	oo_DOUBLE (ymin)
-	oo_DOUBLE (ymax)
-	oo_LONG (ny)
-	oo_DOUBLE (dy)
-	oo_DOUBLE (y1)
-	#if oo_READING
-		if (Melder_debug == 45)
-			Melder_casual ("structMatrix :: read: Going to read %ld rows of %ld columns.", ny, nx);
-		if (localVersion >= 2) {
-			oo_DOUBLE_MATRIX (z, ny, nx)
-		} else {
-			oo_FLOAT_MATRIX (z, ny, nx)
-		}
-	#else
-		oo_DOUBLE_MATRIX (z, ny, nx)
-	#endif
+#define ooSTRUCT Photo
+oo_DEFINE_CLASS (Photo, SampledXY)
+
+	oo_OBJECT (Matrix, 2, d_red)
+	oo_OBJECT (Matrix, 2, d_green)
+	oo_OBJECT (Matrix, 2, d_blue)
+	oo_OBJECT (Matrix, 2, d_transparency)
 
 	#if oo_DECLARING
 		// overridden methods:
@@ -48,12 +36,45 @@ oo_DEFINE_CLASS (Matrix, Sampled)
 			virtual bool v_hasGetNy        () { return true; }   virtual double v_getNy        ()        { return ny; }
 			virtual bool v_hasGetDy        () { return true; }   virtual double v_getDy        ()        { return dy; }
 			virtual bool v_hasGetY         () { return true; }   virtual double v_getY         (long iy) { return y1 + (iy - 1) * dy; }
-			virtual bool v_hasGetMatrix    () { return true; }   virtual double v_getMatrix    (long irow, long icol);
-			virtual bool v_hasGetFunction2 () { return true; }   virtual double v_getFunction2 (double x, double y);
-			virtual double v_getValueAtSample (long isamp, long ilevel, int unit);
+		// functions:
+			void f_init (double xmin, double xmax, long nx, double dx, double x1,
+			             double ymin, double ymax, long ny, double dy, double y1);
+
+			double_rgbt f_getValueAtXY (double x, double y);
+			/*
+				Linear interpolation between matrix points,
+				constant extrapolation in cells on the edge,
+				NUMundefined outside the union of the unit squares around the points.
+			*/
+
+			void f_replaceRed (Matrix red);
+			void f_replaceGreen (Matrix green);
+			void f_replaceBlue (Matrix blue);
+			void f_replaceTransparency (Matrix transparency);
+
+			void f_paintImage (Graphics g, double xmin, double xmax, double ymin, double ymax);
+
+			void f_paintCells (Graphics g, double xmin, double xmax, double ymin, double ymax);
+			/*
+				Every sample is drawn as a rectangle.
+			*/
+
+			void f_movie (Graphics g);
+			void f_saveAsPNG               (MelderFile file);
+			void f_saveAsTIFF              (MelderFile file);
+			void f_saveAsGIF               (MelderFile file);
+			void f_saveAsWindowsBitmapFile (MelderFile file);
+			void f_saveAsJPEG              (MelderFile file);
+			void f_saveAsJPEG2000          (MelderFile file);
+			void f_saveAsAppleIconFile     (MelderFile file);
+			void f_saveAsWindowsIconFile   (MelderFile file);
+		// helpers:
+			#if defined (macintosh)
+				void _mac_saveAsImageFile (MelderFile file, const void *which);
+			#endif
 	#endif
 
-oo_END_CLASS (Matrix)
+oo_END_CLASS (Photo)
 #undef ooSTRUCT
 
 
diff --git a/fon/Sampled.h b/fon/Sampled.h
index c7eea5c..acbb71f 100644
--- a/fon/Sampled.h
+++ b/fon/Sampled.h
@@ -61,8 +61,6 @@ void Sampled_shortTermAnalysis (Sampled me, double windowDuration, double timeSt
 			at least 1 (if no failure); equals floor ((nx * dx - windowDuration) / timeStep) + 1.
 		firstTime:
 			the centre of the first frame, in seconds.
-	Return value:
-		1 = OK, 0 = failure.
 	Failures:
 		Window longer than signal.
 	Postconditions:
diff --git a/fon/SampledXY.cpp b/fon/SampledXY.cpp
new file mode 100644
index 0000000..9890dd8
--- /dev/null
+++ b/fon/SampledXY.cpp
@@ -0,0 +1,64 @@
+/* SampledXY.cpp
+ *
+ * Copyright (C) 1992-2012,2013 Paul Boersma
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "SampledXY.h"
+
+#include "oo_DESTROY.h"
+#include "SampledXY_def.h"
+#include "oo_COPY.h"
+#include "SampledXY_def.h"
+#include "oo_EQUAL.h"
+#include "SampledXY_def.h"
+#include "oo_CAN_WRITE_AS_ENCODING.h"
+#include "SampledXY_def.h"
+#include "oo_WRITE_TEXT.h"
+#include "SampledXY_def.h"
+#include "oo_READ_TEXT.h"
+#include "SampledXY_def.h"
+#include "oo_WRITE_BINARY.h"
+#include "SampledXY_def.h"
+#include "oo_READ_BINARY.h"
+#include "SampledXY_def.h"
+#include "oo_DESCRIPTION.h"
+#include "SampledXY_def.h"
+
+Thing_implement (SampledXY, Sampled, 0);
+
+void structSampledXY :: f_init
+	(double xmin, double xmax, long nx, double dx, double x1,
+	 double ymin, double ymax, long ny, double dy, double y1)
+{
+	Sampled_init (this, xmin, xmax, nx, dx, x1);
+	this -> ymin = ymin;
+	this -> ymax = ymax;
+	this -> ny = ny;
+	this -> dy = dy;
+	this -> y1 = y1;
+}
+
+long structSampledXY :: f_getWindowSamplesY (double ymin, double ymax, long *iymin, long *iymax) {
+	*iymin = 1 + (long) ceil  ((ymin - this -> y1) / this -> dy);
+	*iymax = 1 + (long) floor ((ymax - this -> y1) / this -> dy);
+	if (*iymin < 1) *iymin = 1;
+	if (*iymax > this -> ny) *iymax = this -> ny;
+	if (*iymin > *iymax) return 0;
+	return *iymax - *iymin + 1;
+}
+
+/* End of file SampledXY.cpp */
diff --git a/fon/SampledXY.h b/fon/SampledXY.h
new file mode 100644
index 0000000..2ee0597
--- /dev/null
+++ b/fon/SampledXY.h
@@ -0,0 +1,62 @@
+#ifndef _SampledXY_h_
+#define _SampledXY_h_
+/* SampledXY.h
+ *
+ * Copyright (C) 1992-2011,2013 Paul Boersma
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "Sampled.h"
+
+#include "SampledXY_def.h"
+oo_CLASS_CREATE (SampledXY, Sampled);
+
+double Matrix_columnToX (I, double column);   /* Return my x1 + (column - 1) * my dx.	 */
+
+double Matrix_rowToY (I, double row);   /* Return my y1 + (row - 1) * my dy. */
+
+double Matrix_xToColumn (I, double x);   /* Return (x - xmin) / my dx + 1. */
+
+long Matrix_xToLowColumn (I, double x);   /* Return floor (Matrix_xToColumn (me, x)). */
+
+long Matrix_xToHighColumn (I, double x);   /* Return ceil (Matrix_xToColumn (me, x)). */
+
+long Matrix_xToNearestColumn (I, double x);   /* Return floor (Matrix_xToColumn (me, x) + 0.5). */
+
+double Matrix_yToRow (I, double y);   /* Return (y - ymin) / my dy + 1. */
+
+long Matrix_yToLowRow (I, double y);   /* Return floor (Matrix_yToRow (me, y)). */
+
+long Matrix_yToHighRow (I, double x);   /* Return ceil (Matrix_yToRow (me, y)). */
+
+long Matrix_yToNearestRow (I, double y);   /* Return floor (Matrix_yToRow (me, y) + 0.5). */
+
+long Matrix_getWindowSamplesX (I, double xmin, double xmax, long *ixmin, long *ixmax);
+/*
+	Function:
+		return the number of samples with x values in [xmin, xmax].
+		Put the first of these samples in ixmin.
+		Put the last of these samples in ixmax.
+	Postconditions:
+		*ixmin >= 1;
+		*ixmax <= my nx;
+		if (result != 0) *ixmin <= *ixmax; else *ixmin > *ixmax;
+		if (result != 0) result == *ixmax - *ixmin + 1;
+*/
+long Matrix_getWindowSamplesY (I, double ymin, double ymax, long *iymin, long *iymax);
+
+/* End of file SampledXY.h */
+#endif
diff --git a/fon/Matrix_def.h b/fon/SampledXY_def.h
similarity index 59%
copy from fon/Matrix_def.h
copy to fon/SampledXY_def.h
index 87d3408..84ad0a5 100644
--- a/fon/Matrix_def.h
+++ b/fon/SampledXY_def.h
@@ -1,6 +1,6 @@
-/* Matrix_def.h
+/* SampledXY_def.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,44 +17,40 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+/*
+ * pb 20131022 split off from Matrix
+ */
 
-#define ooSTRUCT Matrix
-oo_DEFINE_CLASS (Matrix, Sampled)
+#define ooSTRUCT SampledXY
+oo_DEFINE_CLASS (SampledXY, Sampled)
 
 	oo_DOUBLE (ymin)
 	oo_DOUBLE (ymax)
 	oo_LONG (ny)
 	oo_DOUBLE (dy)
 	oo_DOUBLE (y1)
-	#if oo_READING
-		if (Melder_debug == 45)
-			Melder_casual ("structMatrix :: read: Going to read %ld rows of %ld columns.", ny, nx);
-		if (localVersion >= 2) {
-			oo_DOUBLE_MATRIX (z, ny, nx)
-		} else {
-			oo_FLOAT_MATRIX (z, ny, nx)
-		}
-	#else
-		oo_DOUBLE_MATRIX (z, ny, nx)
-	#endif
 
 	#if oo_DECLARING
 		// overridden methods:
-			virtual void v_info ();
-			virtual bool v_hasGetNrow      () { return true; }   virtual double v_getNrow      ()        { return ny; }
-			virtual bool v_hasGetNcol      () { return true; }   virtual double v_getNcol      ()        { return nx; }
+			//virtual void v_info ();
 			virtual bool v_hasGetYmin      () { return true; }   virtual double v_getYmin      ()        { return ymin; }
 			virtual bool v_hasGetYmax      () { return true; }   virtual double v_getYmax      ()        { return ymax; }
 			virtual bool v_hasGetNy        () { return true; }   virtual double v_getNy        ()        { return ny; }
 			virtual bool v_hasGetDy        () { return true; }   virtual double v_getDy        ()        { return dy; }
 			virtual bool v_hasGetY         () { return true; }   virtual double v_getY         (long iy) { return y1 + (iy - 1) * dy; }
-			virtual bool v_hasGetMatrix    () { return true; }   virtual double v_getMatrix    (long irow, long icol);
-			virtual bool v_hasGetFunction2 () { return true; }   virtual double v_getFunction2 (double x, double y);
-			virtual double v_getValueAtSample (long isamp, long ilevel, int unit);
+		// functions:
+			void f_init (double xmin, double xmax, long nx, double dx, double x1,
+			             double ymin, double ymax, long ny, double dy, double y1);
+			double f_indexToY (long index) { return y1 + (index - 1) * dy; }
+			double f_yToIndex (double y) { return (y - y1) / dy + 1; }
+			long f_yToLowIndex     (double y) { return (long) floor (f_yToIndex (y)); }
+			long f_yToHighIndex    (double y) { return (long) ceil  (f_yToIndex (y)); }
+			long f_yToNearestIndex (double y) { return (long) round (f_yToIndex (y)); }
+			long f_getWindowSamplesY (double ymin, double ymax, long *iymin, long *iymax);
 	#endif
 
-oo_END_CLASS (Matrix)
+oo_END_CLASS (SampledXY)
 #undef ooSTRUCT
 
 
-/* End of file Matrix_def.h */
+/* End of file SampledXY_def.h */
diff --git a/fon/manual_tutorials.cpp b/fon/manual_tutorials.cpp
index e05b288..28cb3ee 100644
--- a/fon/manual_tutorials.cpp
+++ b/fon/manual_tutorials.cpp
@@ -23,9 +23,12 @@
 void manual_tutorials_init (ManPages me);
 void manual_tutorials_init (ManPages me) {
 
-MAN_BEGIN (L"What's new?", L"ppgb", 20130915)
+MAN_BEGIN (L"What's new?", L"ppgb", 20130927)
 INTRO (L"Latest changes in Praat.")
 /*LIST_ITEM (L"\\bu Manual page about @@drawing a vowel triangle at .")*/
+NORMAL (L"##5.3.57# (27 October 2013)")
+LIST_ITEM (L"\\bu Mac: opening, modifying and saving image files (the Photo object).")
+LIST_ITEM (L"\\bu Mac 64-bit: some small improvements in the user interface.")
 NORMAL (L"##5.3.56# (15 September 2013)")
 LIST_ITEM (L"\\bu Mac: 64-bit alpha version.")
 LIST_ITEM (L"\\bu Linux: improved selecting in the Picture window.")
diff --git a/fon/praat_Fon.cpp b/fon/praat_Fon.cpp
index 710cc44..7c29051 100644
--- a/fon/praat_Fon.cpp
+++ b/fon/praat_Fon.cpp
@@ -41,6 +41,7 @@
 #include "Matrix_and_Polygon.h"
 #include "MovieWindow.h"
 #include "ParamCurve.h"
+#include "Photo.h"
 #include "Pitch_Intensity.h"
 #include "Pitch_to_PitchTier.h"
 #include "Pitch_to_PointProcess.h"
@@ -3049,6 +3050,280 @@ END
 
 DIRECT (ParamCurve_help) Melder_help (L"ParamCurve"); END
 
+/***** PHOTO *****/
+
+FORM (Photo_create, L"Create Photo", L"Create Photo...")
+	WORD (L"Name", L"xy")
+	REAL (L"xmin", L"1.0")
+	REAL (L"xmax", L"1.0")
+	NATURAL (L"Number of columns", L"1")
+	POSITIVE (L"dx", L"1.0")
+	REAL (L"x1", L"1.0")
+	REAL (L"ymin", L"1.0")
+	REAL (L"ymax", L"1.0")
+	NATURAL (L"Number of rows", L"1")
+	POSITIVE (L"dy", L"1.0")
+	REAL (L"y1", L"1.0")
+	LABEL (L"", L"Red formula:")
+	TEXTFIELD (L"redFormula", L"x*y")
+	LABEL (L"", L"Green formula:")
+	TEXTFIELD (L"greenFormula", L"x*y")
+	LABEL (L"", L"Blue formula:")
+	TEXTFIELD (L"blueFormula", L"x*y")
+	OK
+DO
+	double xmin = GET_REAL (L"xmin"), xmax = GET_REAL (L"xmax");
+	double ymin = GET_REAL (L"ymin"), ymax = GET_REAL (L"ymax");
+	if (xmax < xmin) Melder_throw ("xmax (", Melder_single (xmax), ") should not be less than xmin (", Melder_single (xmin), ").");
+	if (ymax < ymin) Melder_throw ("ymax (", Melder_single (ymax), ") should not be less than ymin (", Melder_single (ymin), ").");
+	autoPhoto me = Photo_create (
+		xmin, xmax, GET_INTEGER (L"Number of columns"), GET_REAL (L"dx"), GET_REAL (L"x1"),
+		ymin, ymax, GET_INTEGER (L"Number of rows"), GET_REAL (L"dy"), GET_REAL (L"y1"));
+	Matrix_formula (my d_red,   GET_STRING (L"redFormula"),   interpreter, NULL);
+	Matrix_formula (my d_green, GET_STRING (L"greenFormula"), interpreter, NULL);
+	Matrix_formula (my d_blue,  GET_STRING (L"blueFormula"),  interpreter, NULL);
+	praat_new (me.transfer(), GET_STRING (L"Name"));
+END
+
+FORM (Photo_createSimple, L"Create simple Photo", L"Create simple Photo...")
+	WORD (L"Name", L"xy")
+	NATURAL (L"Number of rows", L"10")
+	NATURAL (L"Number of columns", L"10")
+	LABEL (L"", L"Red formula:")
+	TEXTFIELD (L"redFormula", L"x*y")
+	LABEL (L"", L"Green formula:")
+	TEXTFIELD (L"greenFormula", L"x*y")
+	LABEL (L"", L"Blue formula:")
+	TEXTFIELD (L"blueFormula", L"x*y")
+	OK
+DO
+	autoPhoto me = Photo_createSimple (GET_INTEGER (L"Number of rows"), GET_INTEGER (L"Number of columns"));
+	Matrix_formula (my d_red,   GET_STRING (L"redFormula"),   interpreter, NULL);
+	Matrix_formula (my d_green, GET_STRING (L"greenFormula"), interpreter, NULL);
+	Matrix_formula (my d_blue,  GET_STRING (L"blueFormula"),  interpreter, NULL);
+	praat_new (me.transfer(), GET_STRING (L"Name"));
+END
+
+DIRECT (Photo_extractBlue)
+	LOOP {
+		iam (Photo);
+		autoMatrix thee = Data_copy (my d_blue);
+		praat_new (thee.transfer(), my name, L"_blue");
+	}
+END
+
+DIRECT (Photo_extractGreen)
+	LOOP {
+		iam (Photo);
+		autoMatrix thee = Data_copy (my d_green);
+		praat_new (thee.transfer(), my name, L"_green");
+	}
+END
+
+DIRECT (Photo_extractRed)
+	LOOP {
+		iam (Photo);
+		autoMatrix thee = Data_copy (my d_red);
+		praat_new (thee.transfer(), my name, L"_red");
+	}
+END
+
+DIRECT (Photo_extractTransparency)
+	LOOP {
+		iam (Photo);
+		autoMatrix thee = Data_copy (my d_transparency);
+		praat_new (thee.transfer(), my name, L"_transparency");
+	}
+END
+
+FORM (Photo_formula_red, L"Photo Formula (red)", L"Formula (red)...")
+	LABEL (L"label", L"y := y1; for row := 1 to nrow do { x := x1; "
+		"for col := 1 to ncol do { self [row, col] := `formula' ; x := x + dx } y := y + dy }")
+	TEXTFIELD (L"formula", L"self")
+	OK
+DO
+	LOOP {
+		iam (Photo);
+		try {
+			Matrix_formula (my d_red, GET_STRING (L"formula"), interpreter, NULL);
+			praat_dataChanged (me);
+		} catch (MelderError) {
+			praat_dataChanged (me);   // in case of error, the Photo may have partially changed
+			throw;
+		}
+	}
+END
+
+FORM (Photo_formula_green, L"Photo Formula (green)", L"Formula (green)...")
+	LABEL (L"label", L"y := y1; for row := 1 to nrow do { x := x1; "
+		"for col := 1 to ncol do { self [row, col] := `formula' ; x := x + dx } y := y + dy }")
+	TEXTFIELD (L"formula", L"self")
+	OK
+DO
+	LOOP {
+		iam (Photo);
+		try {
+			Matrix_formula (my d_green, GET_STRING (L"formula"), interpreter, NULL);
+			praat_dataChanged (me);
+		} catch (MelderError) {
+			praat_dataChanged (me);   // in case of error, the Photo may have partially changed
+			throw;
+		}
+	}
+END
+
+FORM (Photo_formula_blue, L"Photo Formula (blue)", L"Formula (blue)...")
+	LABEL (L"label", L"y := y1; for row := 1 to nrow do { x := x1; "
+		"for col := 1 to ncol do { self [row, col] := `formula' ; x := x + dx } y := y + dy }")
+	TEXTFIELD (L"formula", L"self")
+	OK
+DO
+	LOOP {
+		iam (Photo);
+		try {
+			Matrix_formula (my d_blue, GET_STRING (L"formula"), interpreter, NULL);
+			praat_dataChanged (me);
+		} catch (MelderError) {
+			praat_dataChanged (me);   // in case of error, the Photo may have partially changed
+			throw;
+		}
+	}
+END
+
+FORM (Photo_formula_transparency, L"Photo Formula (transparency)", L"Formula (transparency)...")
+	LABEL (L"label", L"y := y1; for row := 1 to nrow do { x := x1; "
+		"for col := 1 to ncol do { self [row, col] := `formula' ; x := x + dx } y := y + dy }")
+	TEXTFIELD (L"formula", L"self")
+	OK
+DO
+	LOOP {
+		iam (Photo);
+		try {
+			Matrix_formula (my d_transparency, GET_STRING (L"formula"), interpreter, NULL);
+			praat_dataChanged (me);
+		} catch (MelderError) {
+			praat_dataChanged (me);   // in case of error, the Photo may have partially changed
+			throw;
+		}
+	}
+END
+
+FORM (Photo_paintCells, L"Photo: Paint cells with colour", L"Photo: Paint cells...")
+	REAL (L"From x =", L"0.0")
+	REAL (L"To x =", L"0.0")
+	REAL (L"From y =", L"0.0")
+	REAL (L"To y =", L"0.0")
+	OK
+DO
+	LOOP {
+		iam (Photo);
+		autoPraatPicture picture;
+		my f_paintCells (GRAPHICS,
+			GET_REAL (L"From x ="), GET_REAL (L"To x ="), GET_REAL (L"From y ="), GET_REAL (L"To y ="));
+	}
+END
+
+FORM (Photo_paintImage, L"Photo: Paint colour image", 0)
+	REAL (L"From x =", L"0.0")
+	REAL (L"To x =", L"0.0")
+	REAL (L"From y =", L"0.0")
+	REAL (L"To y =", L"0.0")
+	OK
+DO
+	LOOP {
+		iam (Photo);
+		autoPraatPicture picture;
+		my f_paintImage (GRAPHICS,
+			GET_REAL (L"From x ="), GET_REAL (L"To x ="), GET_REAL (L"From y ="), GET_REAL (L"To y ="));
+	}
+END
+
+FORM_WRITE (Photo_saveAsAppleIconFile, L"Save as Apple icon file", 0, L"icns")
+	LOOP {
+		iam (Photo);
+		my f_saveAsAppleIconFile (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsGIF, L"Save as GIF file", 0, L"gif")
+	LOOP {
+		iam (Photo);
+		my f_saveAsGIF (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsJPEG, L"Save as JPEG file", 0, L"jpg")
+	LOOP {
+		iam (Photo);
+		my f_saveAsJPEG (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsJPEG2000, L"Save as JPEG-2000 file", 0, L"jpg")
+	LOOP {
+		iam (Photo);
+		my f_saveAsJPEG2000 (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsPNG, L"Save as PNG file", 0, L"png")
+	LOOP {
+		iam (Photo);
+		my f_saveAsPNG (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsTIFF, L"Save as TIFF file", 0, L"tiff")
+	LOOP {
+		iam (Photo);
+		my f_saveAsTIFF (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsWindowsBitmapFile, L"Save as Windows bitmap file", 0, L"bmp")
+	LOOP {
+		iam (Photo);
+		my f_saveAsWindowsBitmapFile (file);
+	}
+END
+
+FORM_WRITE (Photo_saveAsWindowsIconFile, L"Save as Windows icon file", 0, L"ico")
+	LOOP {
+		iam (Photo);
+		my f_saveAsWindowsIconFile (file);
+	}
+END
+
+/***** PHOTO & MATRIX *****/
+
+DIRECT (Photo_Matrix_replaceBlue)
+	Photo me = FIRST (Photo);
+	Matrix thee = FIRST (Matrix);
+	my f_replaceBlue (thee);
+	praat_dataChanged (me);
+END
+
+DIRECT (Photo_Matrix_replaceGreen)
+	Photo me = FIRST (Photo);
+	Matrix thee = FIRST (Matrix);
+	my f_replaceGreen (thee);
+	praat_dataChanged (me);
+END
+
+DIRECT (Photo_Matrix_replaceRed)
+	Photo me = FIRST (Photo);
+	Matrix thee = FIRST (Matrix);
+	my f_replaceRed (thee);
+	praat_dataChanged (me);
+END
+
+DIRECT (Photo_Matrix_replaceTransparency)
+	Photo me = FIRST (Photo);
+	Matrix thee = FIRST (Matrix);
+	my f_replaceTransparency (thee);
+	praat_dataChanged (me);
+END
+
 /***** PITCH *****/
 
 DIRECT (Pitch_getNumberOfVoicedFrames)
@@ -5984,6 +6259,16 @@ static Any chronologicalTextGridTextFileRecognizer (int nread, const char *heade
 	return NULL;
 }
 
+static Any imageFileRecognizer (int nread, const char *header, MelderFile file) {
+	const wchar_t *fileName = MelderFile_name (file);
+	(void) header;
+	if (wcsstr (fileName, L".jpg") || wcsstr (fileName, L".JPG") || wcsstr (fileName, L".png") || wcsstr (fileName, L".PNG") ||
+	    wcsstr (fileName, L".tiff") || wcsstr (fileName, L".TIFF") || wcsstr (fileName, L".tif") || wcsstr (fileName, L".TIFF")) {
+		return Photo_readFromImageFile (file);
+	}
+	return NULL;
+}
+
 /***** buttons *****/
 
 void praat_TableOfReal_init (ClassInfo klas);   // Buttons for TableOfReal and for its subclasses.
@@ -6047,13 +6332,13 @@ void praat_uvafon_init () {
 	Thing_recognizeClassesByName (classSound, classMatrix, classPolygon, classPointProcess, classParamCurve,
 		classSpectrum, classLtas, classSpectrogram, classFormant,
 		classExcitation, classCochleagram, classVocalTract, classFormantPoint, classFormantTier, classFormantGrid,
-		classLabel, classTier, classAutosegment,   /* Three obsolete classes. */
+		classLabel, classTier, classAutosegment,   // three obsolete classes
 		classIntensity, classPitch, classHarmonicity,
 		classTransition,
 		classRealPoint, classRealTier, classPitchTier, classIntensityTier, classDurationTier, classAmplitudeTier, classSpectrumTier,
 		classManipulation, classTextPoint, classTextInterval, classTextTier,
 		classIntervalTier, classTextGrid, classLongSound, classWordList, classSpellingChecker,
-		classMovie, classCorpus,
+		classMovie, classCorpus, classPhoto,
 		NULL);
 	Thing_recognizeClassByOtherName (classManipulation, L"Psola");
 	Thing_recognizeClassByOtherName (classManipulation, L"Analysis");
@@ -6061,6 +6346,7 @@ void praat_uvafon_init () {
 
 	Data_recognizeFileType (cgnSyntaxFileRecognizer);
 	Data_recognizeFileType (chronologicalTextGridTextFileRecognizer);
+	Data_recognizeFileType (imageFileRecognizer);
 
 	structManipulationEditor :: f_preferences ();
 	structSpectrumEditor     :: f_preferences ();
@@ -6075,6 +6361,9 @@ void praat_uvafon_init () {
 	praat_addMenuCommand (L"Objects", L"New", L"Matrix", 0, 0, 0);
 		praat_addMenuCommand (L"Objects", L"New", L"Create Matrix...", 0, 1, DO_Matrix_create);
 		praat_addMenuCommand (L"Objects", L"New", L"Create simple Matrix...", 0, 1, DO_Matrix_createSimple);
+		praat_addMenuCommand (L"Objects", L"New", L"-- colour matrix --", 0, 1, 0);
+		praat_addMenuCommand (L"Objects", L"New", L"Create Photo...", 0, 1, DO_Photo_create);
+		praat_addMenuCommand (L"Objects", L"New", L"Create simple Photo...", 0, 1, DO_Photo_createSimple);
 	praat_addMenuCommand (L"Objects", L"Open", L"-- read movie --", 0, praat_HIDDEN, 0);
 	praat_addMenuCommand (L"Objects", L"Open", L"Open movie file...", 0, praat_HIDDEN, DO_Movie_openFromSoundFile);
 	praat_addMenuCommand (L"Objects", L"Open", L"-- read raw --", 0, 0, 0);
@@ -6458,6 +6747,28 @@ praat_addAction1 (classMatrix, 0, L"Analyse", 0, 0, 0);
 	praat_addAction1 (classParamCurve, 0, L"Draw", 0, 0, 0);
 	praat_addAction1 (classParamCurve, 0, L"Draw...", 0, 0, DO_ParamCurve_draw);
 
+	praat_addAction1 (classPhoto, 0, L"Draw -", 0, 0, 0);
+		praat_addAction1 (classPhoto, 0, L"Paint image...", 0, 1, DO_Photo_paintImage);
+		praat_addAction1 (classPhoto, 0, L"Paint cells...", 0, 1, DO_Photo_paintCells);
+	praat_addAction1 (classPhoto, 0, L"Modify -", 0, 0, 0);
+		praat_addAction1 (classPhoto, 0, L"Formula (red)...", 0, 1, DO_Photo_formula_red);
+		praat_addAction1 (classPhoto, 0, L"Formula (green)...", 0, 1, DO_Photo_formula_green);
+		praat_addAction1 (classPhoto, 0, L"Formula (blue)...", 0, 1, DO_Photo_formula_blue);
+		praat_addAction1 (classPhoto, 0, L"Formula (transparency)...", 0, 1, DO_Photo_formula_transparency);
+	praat_addAction1 (classPhoto, 0, L"Extract -", 0, 0, 0);
+		praat_addAction1 (classPhoto, 0, L"Extract red", 0, 1, DO_Photo_extractRed);
+		praat_addAction1 (classPhoto, 0, L"Extract green", 0, 1, DO_Photo_extractGreen);
+		praat_addAction1 (classPhoto, 0, L"Extract blue", 0, 1, DO_Photo_extractBlue);
+		praat_addAction1 (classPhoto, 0, L"Extract transparency", 0, 1, DO_Photo_extractTransparency);
+	praat_addAction1 (classPhoto, 1, L"Save as PNG file...", 0, 0, DO_Photo_saveAsPNG);
+	praat_addAction1 (classPhoto, 1, L"Save as TIFF file...", 0, 0, DO_Photo_saveAsTIFF);
+	praat_addAction1 (classPhoto, 1, L"Save as GIF file...", 0, 0, DO_Photo_saveAsGIF);
+	praat_addAction1 (classPhoto, 1, L"Save as Windows bitmap file...", 0, 0, DO_Photo_saveAsWindowsBitmapFile);
+	praat_addAction1 (classPhoto, 1, L"Save as JPEG file...", 0, 0, DO_Photo_saveAsJPEG);
+	praat_addAction1 (classPhoto, 1, L"Save as JPEG-2000 file...", 0, 0, DO_Photo_saveAsJPEG2000);
+	praat_addAction1 (classPhoto, 1, L"Save as Apple icon file...", 0, 0, DO_Photo_saveAsAppleIconFile);
+	praat_addAction1 (classPhoto, 1, L"Save as Windows icon file...", 0, 0, DO_Photo_saveAsWindowsIconFile);
+
 	praat_addAction1 (classPitch, 0, L"Pitch help", 0, 0, DO_Pitch_help);
 	praat_addAction1 (classPitch, 1, L"View & Edit", 0, praat_ATTRACTIVE, DO_Pitch_edit);
 	praat_addAction1 (classPitch, 1, L"Edit", 0, praat_HIDDEN, DO_Pitch_edit);
@@ -6787,6 +7098,10 @@ praat_addAction1 (classTransition, 0, L"Cast", 0, 0, 0);
 	praat_addAction2 (classManipulation, 1, classDurationTier, 1, L"Replace duration tier", 0, 0, DO_Manipulation_replaceDurationTier);
 	praat_addAction2 (classManipulation, 1, classTextTier, 1, L"To Manipulation", 0, 0, DO_Manipulation_TextTier_to_Manipulation);
 	praat_addAction2 (classMatrix, 1, classSound, 1, L"To ParamCurve", 0, 0, DO_Matrix_to_ParamCurve);
+	praat_addAction2 (classPhoto, 1, classMatrix, 1, L"Replace red", 0, 0, DO_Photo_Matrix_replaceRed);
+	praat_addAction2 (classPhoto, 1, classMatrix, 1, L"Replace green", 0, 0, DO_Photo_Matrix_replaceGreen);
+	praat_addAction2 (classPhoto, 1, classMatrix, 1, L"Replace blue", 0, 0, DO_Photo_Matrix_replaceBlue);
+	praat_addAction2 (classPhoto, 1, classMatrix, 1, L"Replace transparency", 0, 0, DO_Photo_Matrix_replaceTransparency);
 	praat_addAction2 (classPitch, 1, classPitchTier, 1, L"Draw...", 0, 0, DO_PitchTier_Pitch_draw);
 	praat_addAction2 (classPitch, 1, classPitchTier, 1, L"To Pitch", 0, 0, DO_Pitch_PitchTier_to_Pitch);
 	praat_addAction2 (classPitch, 1, classPointProcess, 1, L"To PitchTier", 0, 0, DO_Pitch_PointProcess_to_PitchTier);
diff --git a/main/praat_mac.plist b/main/praat_mac.plist
index 30f61e6..d5e0498 100644
--- a/main/praat_mac.plist
+++ b/main/praat_mac.plist
@@ -410,6 +410,42 @@
 			<key>NSPersistentStoreTypeKey</key>
 			<string>NSBinaryStoreType</string>
 		</dict>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>jpg</string>
+			</array>
+			<key>CFBundleTypeName</key>
+			<string>JPEG image</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>NSBinaryStoreType</string>
+		</dict>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>png</string>
+			</array>
+			<key>CFBundleTypeName</key>
+			<string>PNG image</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>NSBinaryStoreType</string>
+		</dict>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>tiff</string>
+			</array>
+			<key>CFBundleTypeName</key>
+			<string>TIFF image</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>NSBinaryStoreType</string>
+		</dict>
 	</array>
 	<key>CFBundleExecutable</key>
 	<string>Praat</string>
@@ -434,7 +470,7 @@
 	<key>LSApplicationCategoryType</key>
 	<string>public.app-category.education</string>
 	<key>LSRequiresCarbon</key>
-	<true/>
+	<false/>
 	<key>NSHumanReadableCopyright</key>
 	<string>Copyright © 1992-PRAAT_YEAR by Paul Boersma and David Weenink</string>
 </dict>
diff --git a/makefiles/makefile.defs.linux.alsa b/makefiles/makefile.defs.linux.alsa
index 86500a9..c43b651 100644
--- a/makefiles/makefile.defs.linux.alsa
+++ b/makefiles/makefile.defs.linux.alsa
@@ -1,7 +1,7 @@
 # File: makefile.defs.linux.alsa
 
 # System: Linux
-# Paul Boersma, 24 August 2013
+# Paul Boersma, 26 October 2013
 
 CC = gcc -std=gnu99
 
@@ -15,7 +15,7 @@ LINK = g++
 
 EXECUTABLE = praat
 
-LIBS = `pkg-config --libs gtk+-2.0` -lm -lasound
+LIBS = `pkg-config --libs gtk+-2.0` -lm -lasound -lpthread
 
 AR = ar
 RANLIB = ls
diff --git a/makefiles/makefile.defs.linux.silent b/makefiles/makefile.defs.linux.silent
index ea6fbd5..c3baa5c 100644
--- a/makefiles/makefile.defs.linux.silent
+++ b/makefiles/makefile.defs.linux.silent
@@ -1,7 +1,7 @@
 # File: makefile.defs.linux.silent
 
 # System: Linux without sound
-# Paul Boersma, 24 August 2013
+# Paul Boersma, 26 October 2013
 
 CC = gcc -std=gnu99
 
@@ -15,7 +15,7 @@ LINK = g++
 
 EXECUTABLE = praat
 
-LIBS = `pkg-config --libs gtk+-2.0` -lm
+LIBS = `pkg-config --libs gtk+-2.0` -lm -lpthread
 
 AR = ar
 RANLIB = ls
diff --git a/num/NUM.h b/num/NUM.h
index c747774..f95d691 100644
--- a/num/NUM.h
+++ b/num/NUM.h
@@ -2,7 +2,7 @@
 #define _NUM_h_
 /* NUM.h
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -123,7 +123,7 @@ void NUMscale (double *x, double xminfrom, double xmaxfrom, double xminto, doubl
 #define NUMundefined  HUGE_VAL
 #define NUMdefined(x)  ((x) != NUMundefined)
 
-/********** Arrays with one index (NUMarrays.c) **********/
+/********** Arrays with one index (NUMarrays.cpp) **********/
 
 void * NUMvector (long elementSize, long lo, long hi);
 /*
@@ -173,7 +173,7 @@ void NUMvector_insert (long elementSize, void **v, long lo, long *hi, long posit
 	On failure, *v and *hi are not changed.
 */
 
-/********** Arrays with two indices (NUMarrays.c) **********/
+/********** Arrays with two indices (NUMarrays.cpp) **********/
 
 void * NUMmatrix (long elementSize, long row1, long row2, long col1, long col2);
 /*
diff --git a/praat64_xcodeproj.dmg b/praat64_xcodeproj.dmg
deleted file mode 100644
index f4262f6..0000000
Binary files a/praat64_xcodeproj.dmg and /dev/null differ
diff --git a/praat_xcodeproj.dmg b/praat_xcodeproj.dmg
deleted file mode 100644
index ebc6b87..0000000
Binary files a/praat_xcodeproj.dmg and /dev/null differ
diff --git a/sys/Formula.cpp b/sys/Formula.cpp
index 81a73f3..a1b618b 100644
--- a/sys/Formula.cpp
+++ b/sys/Formula.cpp
@@ -33,11 +33,6 @@
 #include "UiPause.h"
 #include "DemoEditor.h"
 
-#undef our
-#define our ((Data_Table) my methods) ->
-#undef your
-#define your ((Data_Table) thy methods) ->
-
 static Interpreter theInterpreter, theLocalInterpreter;
 static Data theSource;
 static const wchar_t *theExpression;
diff --git a/sys/Graphics.h b/sys/Graphics.h
index eb8e4a0..fd347f7 100644
--- a/sys/Graphics.h
+++ b/sys/Graphics.h
@@ -188,10 +188,14 @@ double Graphics_textWidth_ps_mm (Graphics me, const wchar_t *text, bool useSilip
 void Graphics_fillArea (Graphics me, long numberOfPoints, double *x, double *y);
 void Graphics_cellArray (Graphics me, double **z, long ix1, long ix2, double x1, double x2,
 	long iy1, long iy2, double y1, double y2, double minimum, double maximum);
+void Graphics_cellArray_colour (Graphics me, double_rgbt **z, long ix1, long ix2, double x1, double x2,
+	long iy1, long iy2, double y1, double y2, double minimum, double maximum);
 void Graphics_cellArray8 (Graphics me, unsigned char **z, long ix1, long ix2, double x1, double x2,
 	long iy1, long iy2, double y1, double y2, unsigned char minimum, unsigned char maximum);
 void Graphics_image (Graphics me, double **z, long ix1, long ix2, double x1, double x2,
 	long iy1, long iy2, double y1, double y2, double minimum, double maximum);
+void Graphics_image_colour (Graphics me, double_rgbt **z, long ix1, long ix2, double x1, double x2,
+	long iy1, long iy2, double y1, double y2, double minimum, double maximum);
 void Graphics_image8 (Graphics me, unsigned char **z, long ix1, long ix2, double x1, double x2,
 	long iy1, long iy2, double y1, double y2, unsigned char minimum, unsigned char maximum);
 void Graphics_imageFromFile (Graphics me, const wchar_t *relativeFileName, double x1, double x2, double y1, double y2);
diff --git a/sys/GraphicsP.h b/sys/GraphicsP.h
index b221dc9..8c293bd 100644
--- a/sys/GraphicsP.h
+++ b/sys/GraphicsP.h
@@ -2,7 +2,7 @@
 #define _GraphicsP_h_
 /* GraphicsP.h
  *
- * Copyright (C) 1992-2011,2012 Paul Boersma, 2013 Tom Naughton
+ * Copyright (C) 1992-2011,2012,2013 Paul Boersma, 2013 Tom Naughton
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -162,7 +162,7 @@ enum opcode { SET_VIEWPORT = 101, SET_INNER, UNSET_INNER, SET_WINDOW,
 	/* 148 */ BUTTON, ROUNDED_RECTANGLE, FILL_ROUNDED_RECTANGLE, FILL_ARC,
 	/* 152 */ INNER_RECTANGLE, CELL_ARRAY8, IMAGE, HIGHLIGHT2, UNHIGHLIGHT2,
 	/* 157 */ SET_ARROW_SIZE, DOUBLE_ARROW, SET_RGB_COLOUR, IMAGE_FROM_FILE,
-	/* 161 */ POLYLINE_CLOSED
+	/* 161 */ POLYLINE_CLOSED, CELL_ARRAY_COLOUR, IMAGE_COLOUR, SET_COLOUR_SCALE
 };
 
 void _GraphicsScreen_text_init (GraphicsScreen me);
diff --git a/sys/GraphicsScreen.cpp b/sys/GraphicsScreen.cpp
index 3338730..b3668b4 100644
--- a/sys/GraphicsScreen.cpp
+++ b/sys/GraphicsScreen.cpp
@@ -196,6 +196,7 @@ void structGraphicsScreen :: v_clearWs () {
             //CGContextSynchronize (context);
             CGContextRestoreGState (context);
 			[cocoaDrawingArea unlockFocus];
+			[cocoaDrawingArea setNeedsDisplay: YES];
         }
 	#elif win
 		RECT rect;
diff --git a/sys/Graphics_image.cpp b/sys/Graphics_image.cpp
index 057f6c9..b9c4902 100644
--- a/sys/Graphics_image.cpp
+++ b/sys/Graphics_image.cpp
@@ -1,6 +1,6 @@
 /* Graphics_image.cpp
  *
- * Copyright (C) 1992-2012 Paul Boersma
+ * Copyright (C) 1992-2012,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@
  * pb 2011/03/17 C++
  * pb 2012/04/21 on PostScript, minimal image resolution raised from 106 to 300 dpi
  * pb 2012/05/08 erased all QuickDraw
+ * pb 2013/10/22 colour
  */
 
 #include "GraphicsP.h"
@@ -46,7 +47,7 @@
 #define wdx(x)  ((x) * my scaleX + my deltaX)
 #define wdy(y)  ((y) * my scaleY + my deltaY)
 
-static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_float, unsigned char **z_byte,
+static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_float, double_rgbt **z_rgbt, unsigned char **z_byte,
 	long ix1, long ix2, long x1DC, long x2DC,
 	long iy1, long iy2, long y1DC, long y2DC,
 	double minimum, double maximum,
@@ -107,22 +108,42 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 				#endif
 				for (ix = ix1; ix <= ix2; ix ++) {
 					long left = lefts [ix], right = lefts [ix + 1];
-					long value = offset - scale * ( z_float ? z_float [iy] [ix] : z_byte [iy] [ix] );
 					if (right < clipx1 || left > clipx2) continue;
 					if (left < clipx1) left = clipx1;
 					if (right > clipx2) right = clipx2;
-					#if cairo
-						cairo_set_source (my d_cairoGraphicsContext, grey [value <= 0 ? 0 : value >= sizeof (grey) / sizeof (*grey) ? sizeof (grey) / sizeof (*grey) : value]);
-						cairo_rectangle (my d_cairoGraphicsContext, left, top, right - left, bottom - top);
-						cairo_fill (my d_cairoGraphicsContext);
-					#elif win
-						rect. left = left; rect. right = right;
-						FillRect (my d_gdiGraphicsContext, & rect, greyBrush [value <= 0 ? 0 : value >= 255 ? 255 : value]);
-					#elif mac
-						double igrey = ( value <= 0 ? 0 : value >= 255 ? 255 : value ) / 255.0;
-						CGContextSetRGBFillColor (my d_macGraphicsContext, igrey, igrey, igrey, 1.0);
-						CGContextFillRect (my d_macGraphicsContext, CGRectMake (left, top, right - left, bottom - top));
-					#endif
+					if (z_rgbt) {
+						#if cairo
+							// NYI
+						#elif win
+							// NYI
+						#elif mac
+							double red          = z_rgbt [iy] [ix]. red;
+							double green        = z_rgbt [iy] [ix]. green;
+							double blue         = z_rgbt [iy] [ix]. blue;
+							double transparency = z_rgbt [iy] [ix]. transparency;
+							red =   ( red   <= 0.0 ? 0.0 : red   >= 1.0 ? 1.0 : red   );
+							green = ( green <= 0.0 ? 0.0 : green >= 1.0 ? 1.0 : green );
+							blue =  ( blue  <= 0.0 ? 0.0 : blue  >= 1.0 ? 1.0 : blue  );
+							CGContextSetRGBFillColor (my d_macGraphicsContext, red, green, blue, 1.0 - transparency);
+							CGContextFillRect (my d_macGraphicsContext, CGRectMake (left, top, right - left, bottom - top));
+						#endif
+					} else {
+						#if cairo
+							long value = offset - scale * ( z_float ? z_float [iy] [ix] : z_byte [iy] [ix] );
+							cairo_set_source (my d_cairoGraphicsContext, grey [value <= 0 ? 0 : value >= sizeof (grey) / sizeof (*grey) ? sizeof (grey) / sizeof (*grey) : value]);
+							cairo_rectangle (my d_cairoGraphicsContext, left, top, right - left, bottom - top);
+							cairo_fill (my d_cairoGraphicsContext);
+						#elif win
+							long value = offset - scale * ( z_float ? z_float [iy] [ix] : z_byte [iy] [ix] );
+							rect. left = left; rect. right = right;
+							FillRect (my d_gdiGraphicsContext, & rect, greyBrush [value <= 0 ? 0 : value >= 255 ? 255 : value]);
+						#elif mac
+							double value = offset - scale * ( z_float ? z_float [iy] [ix] : z_byte [iy] [ix] );
+							double igrey = ( value <= 0 ? 0 : value >= 255 ? 255 : value ) / 255.0;
+							CGContextSetRGBFillColor (my d_macGraphicsContext, igrey, igrey, igrey, 1.0);
+							CGContextFillRect (my d_macGraphicsContext, CGRectMake (left, top, right - left, bottom - top));
+						#endif
+					}
 				}
 			}
 			
@@ -247,6 +268,30 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 							double value = offset - scale * interpol;
 							PUT_PIXEL
 						}
+					} else if (z_rgbt) {
+						double_rgbt *ztop = z_rgbt [itop], *zbottom = z_rgbt [ibottom];
+						for (xDC = clipx1; xDC < clipx2; xDC += undersampling) {
+							double red =
+								rightWeight [xDC] * (topWeight * ztop [iright [xDC]]. red + bottomWeight * zbottom [iright [xDC]]. red) +
+								leftWeight  [xDC] * (topWeight * ztop [ileft  [xDC]]. red + bottomWeight * zbottom [ileft  [xDC]]. red);
+							double green =
+								rightWeight [xDC] * (topWeight * ztop [iright [xDC]]. green + bottomWeight * zbottom [iright [xDC]]. green) +
+								leftWeight  [xDC] * (topWeight * ztop [ileft  [xDC]]. green + bottomWeight * zbottom [ileft  [xDC]]. green);
+							double blue =
+								rightWeight [xDC] * (topWeight * ztop [iright [xDC]]. blue + bottomWeight * zbottom [iright [xDC]]. blue) +
+								leftWeight  [xDC] * (topWeight * ztop [ileft  [xDC]]. blue + bottomWeight * zbottom [ileft  [xDC]]. blue);
+							double transparency =
+								rightWeight [xDC] * (topWeight * ztop [iright [xDC]]. transparency + bottomWeight * zbottom [iright [xDC]]. transparency) +
+								leftWeight  [xDC] * (topWeight * ztop [ileft  [xDC]]. transparency + bottomWeight * zbottom [ileft  [xDC]]. transparency);
+							if (red          < 0.0) red          = 0.0; else if (red          > 1.0) red          = 1.0;
+							if (green        < 0.0) green        = 0.0; else if (green        > 1.0) green        = 1.0;
+							if (blue         < 0.0) blue         = 0.0; else if (blue         > 1.0) blue         = 1.0;
+							if (transparency < 0.0) transparency = 0.0; else if (transparency > 1.0) transparency = 1.0;
+							*pixelAddress ++ = red          * 255.0;
+							*pixelAddress ++ = green        * 255.0;
+							*pixelAddress ++ = blue         * 255.0;
+							*pixelAddress ++ = transparency * 255.0;
+						}
 					} else {
 						unsigned char *ztop = z_byte [itop], *zbottom = z_byte [ibottom];
 						for (xDC = clipx1; xDC < clipx2; xDC += undersampling) {
@@ -357,7 +402,7 @@ static void _GraphicsScreen_cellArrayOrImage (GraphicsScreen me, double **z_floa
 	#endif
 }
 
-static void _GraphicsPostscript_cellArrayOrImage (GraphicsPostscript me, double **z_float, unsigned char **z_byte,
+static void _GraphicsPostscript_cellArrayOrImage (GraphicsPostscript me, double **z_float, double_rgbt **z_rgbt, unsigned char **z_byte,
 	long ix1, long ix2, long x1DC, long x2DC,
 	long iy1, long iy2, long y1DC, long y2DC,
 	double minimum, double maximum,
@@ -526,60 +571,87 @@ static void _GraphicsPostscript_cellArrayOrImage (GraphicsPostscript me, double
 	my d_printf (my d_file, "grestore\n");
 }
 
-static void _cellArrayOrImage (Graphics me, double **z_float, unsigned char **z_byte,
+static void _cellArrayOrImage (Graphics me, double **z_float, double_rgbt **z_rgbt, unsigned char **z_byte,
 	long ix1, long ix2, long x1DC, long x2DC,
 	long iy1, long iy2, long y1DC, long y2DC, double minimum, double maximum,
 	long clipx1, long clipx2, long clipy1, long clipy2, int interpolate)
 {
 	if (my screen) {
-		_GraphicsScreen_cellArrayOrImage (static_cast <GraphicsScreen> (me), z_float, z_byte, ix1, ix2, x1DC, x2DC, iy1, iy2, y1DC, y2DC,
+		_GraphicsScreen_cellArrayOrImage (static_cast <GraphicsScreen> (me), z_float, z_rgbt, z_byte, ix1, ix2, x1DC, x2DC, iy1, iy2, y1DC, y2DC,
 			minimum, maximum, clipx1, clipx2, clipy1, clipy2, interpolate);
 	} else if (my postScript) {
-		_GraphicsPostscript_cellArrayOrImage (static_cast <GraphicsPostscript> (me), z_float, z_byte, ix1, ix2, x1DC, x2DC, iy1, iy2, y1DC, y2DC,
+		_GraphicsPostscript_cellArrayOrImage (static_cast <GraphicsPostscript> (me), z_float, z_rgbt, z_byte, ix1, ix2, x1DC, x2DC, iy1, iy2, y1DC, y2DC,
 			minimum, maximum, clipx1, clipx2, clipy1, clipy2, interpolate);
 	}
 	_Graphics_setColour (me, my colour);
 }
 
-static void cellArrayOrImage (I, double **z_float, unsigned char **z_byte,
+static void cellArrayOrImage (I, double **z_float, double_rgbt **z_rgbt, unsigned char **z_byte,
 	long ix1, long ix2, double x1WC, double x2WC,
 	long iy1, long iy2, double y1WC, double y2WC,
 	double minimum, double maximum, int interpolate)
 {
 	iam (Graphics);
 	if (ix2 < ix1 || iy2 < iy1 || minimum == maximum) return;
-	_cellArrayOrImage (me, z_float, z_byte,
+	_cellArrayOrImage (me, z_float, z_rgbt, z_byte,
 		ix1, ix2, wdx (x1WC), wdx (x2WC),
 		iy1, iy2, wdy (y1WC), wdy (y2WC), minimum, maximum,
 		wdx (my d_x1WC), wdx (my d_x2WC), wdy (my d_y1WC), wdy (my d_y2WC), interpolate);
 	if (my recording) {
-		long nrow = iy2 - iy1 + 1, ncol = ix2 - ix1 + 1, ix, iy;
-		op (interpolate ? ( z_float ? IMAGE : IMAGE8 ) :
-			 (z_float ? CELL_ARRAY : CELL_ARRAY8 ), 8 + nrow * ncol);
-		put (x1WC); put (x2WC); put (y1WC); put (y2WC); put (minimum); put (maximum);
+		long nrow = iy2 - iy1 + 1, ncol = ix2 - ix1 + 1;
+		op (interpolate ? ( z_float ? IMAGE      : z_rgbt ? IMAGE_COLOUR      : IMAGE8 ) :
+		                  ( z_float ? CELL_ARRAY : z_rgbt ? CELL_ARRAY_COLOUR : CELL_ARRAY8 ),
+		    8 + nrow * ncol * ( z_rgbt ? 4 : 1 ));
+		put (x1WC); put (x2WC); put (y1WC); put (y2WC);
+		put (minimum); put (maximum);
 		put (nrow); put (ncol);
-		if (z_float) for (iy = iy1; iy <= iy2; iy ++)
-			{ double *row = z_float [iy]; for (ix = ix1; ix <= ix2; ix ++) put (row [ix]); }
-		else for (iy = iy1; iy <= iy2; iy ++)
-			{ unsigned char *row = z_byte [iy]; for (ix = ix1; ix <= ix2; ix ++) put (row [ix]); }
+		for (long iy = iy1; iy <= iy2; iy ++) {
+			if (z_float) {
+				double *row = z_float [iy];
+				for (long ix = ix1; ix <= ix2; ix ++) {
+					put (row [ix]);
+				}
+			} else if (z_rgbt) {
+				double_rgbt *row = z_rgbt [iy];
+				for (long ix = ix1; ix <= ix2; ix ++) {
+					put (row [ix]. red);
+					put (row [ix]. green);
+					put (row [ix]. blue);
+					put (row [ix]. transparency);
+				}
+			} else {
+				unsigned char *row = z_byte [iy];
+				for (long ix = ix1; ix <= ix2; ix ++) {
+					put (row [ix]);
+				}
+			}
+		}
 	}
 }
 
 void Graphics_cellArray (Graphics me, double **z, long ix1, long ix2, double x1WC, double x2WC,
 	long iy1, long iy2, double y1WC, double y2WC, double minimum, double maximum)
-{ cellArrayOrImage (me, z, NULL, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, FALSE); }
+{ cellArrayOrImage (me, z, NULL, NULL, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, FALSE); }
+
+void Graphics_cellArray_colour (Graphics me, double_rgbt **z, long ix1, long ix2, double x1WC, double x2WC,
+	long iy1, long iy2, double y1WC, double y2WC, double minimum, double maximum)
+{ cellArrayOrImage (me, NULL, z, NULL, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, FALSE); }
 
 void Graphics_cellArray8 (Graphics me, unsigned char **z, long ix1, long ix2, double x1WC, double x2WC,
 	long iy1, long iy2, double y1WC, double y2WC, unsigned char minimum, unsigned char maximum)
-{ cellArrayOrImage (me, NULL, z, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, FALSE); }
+{ cellArrayOrImage (me, NULL, NULL, z, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, FALSE); }
 
 void Graphics_image (Graphics me, double **z, long ix1, long ix2, double x1WC, double x2WC,
 	long iy1, long iy2, double y1WC, double y2WC, double minimum, double maximum)
-{ cellArrayOrImage (me, z, NULL, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, TRUE); }
+{ cellArrayOrImage (me, z, NULL, NULL, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, TRUE); }
+
+void Graphics_image_colour (Graphics me, double_rgbt **z, long ix1, long ix2, double x1WC, double x2WC,
+	long iy1, long iy2, double y1WC, double y2WC, double minimum, double maximum)
+{ cellArrayOrImage (me, NULL, z, NULL, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, TRUE); }
 
 void Graphics_image8 (Graphics me, unsigned char **z, long ix1, long ix2, double x1WC, double x2WC,
 	long iy1, long iy2, double y1WC, double y2WC, unsigned char minimum, unsigned char maximum)
-{ cellArrayOrImage (me, NULL, z, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, TRUE); }
+{ cellArrayOrImage (me, NULL, NULL, z, ix1, ix2, x1WC, x2WC, iy1, iy2, y1WC, y2WC, minimum, maximum, TRUE); }
 
 static void _GraphicsScreen_imageFromFile (GraphicsScreen me, const wchar_t *relativeFileName, double x1, double x2, double y1, double y2) {
 	long x1DC = wdx (x1), x2DC = wdx (x2), y1DC = wdy (y1), y2DC = wdy (y2);
diff --git a/sys/Graphics_record.cpp b/sys/Graphics_record.cpp
index 88a37c9..2d2e1b3 100644
--- a/sys/Graphics_record.cpp
+++ b/sys/Graphics_record.cpp
@@ -1,6 +1,6 @@
 /* Graphics_record.cpp
  *
- * Copyright (C) 1992-2011 Paul Boersma
+ * Copyright (C) 1992-2011,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -359,6 +359,38 @@ void Graphics_play (Graphics me, Graphics thee) {
 			{  long n = get; double *x = mget (n), *y = mget (n);
 				Graphics_polyline_closed (thee, n, & x [1], & y [1]);
 			} break;
+			case CELL_ARRAY_COLOUR:
+			{  double x1 = get, x2 = get, y1 = get, y2 = get, minimum = get, maximum = get;
+				long nrow = get, ncol = get;
+				/*
+				 * We don't copy all the data into a new matrix.
+				 * Instead, we create row pointers z [1..nrow] that point directly into the recorded data.
+				 * This works because the data is a packed array of double_rgbt, just as Graphics_cellArray_colour expects.
+				 */
+				double_rgbt **z = Melder_malloc_f (double_rgbt *, nrow);
+				z [0] = (double_rgbt *) (p + 1);
+				for (long irow = 1; irow < nrow; irow ++) z [irow] = z [irow - 1] + ncol;
+				p += nrow * ncol * 4;
+				Graphics_cellArray_colour (thee, z, 0, ncol - 1, x1, x2,
+								0, nrow - 1, y1, y2, minimum, maximum);
+				Melder_free (z);
+			}  break;
+			case IMAGE_COLOUR:
+			{  double x1 = get, x2 = get, y1 = get, y2 = get, minimum = get, maximum = get;
+				long nrow = get, ncol = get;
+				/*
+				 * We don't copy all the data into a new matrix.
+				 * Instead, we create row pointers z [1..nrow] that point directly into the recorded data.
+				 * This works because the data is a packed array of double_rgbt, just as Graphics_image_colour expects.
+				 */
+				double_rgbt **z = Melder_malloc_f (double_rgbt *, nrow);
+				z [0] = (double_rgbt *) (p + 1);
+				for (long irow = 1; irow < nrow; irow ++) z [irow] = z [irow - 1] + ncol;
+				p += nrow * ncol * 4;
+				Graphics_image_colour (thee, z, 0, ncol - 1, x1, x2,
+								0, nrow - 1, y1, y2, minimum, maximum);
+				Melder_free (z);
+			}  break;
 			default:
 				my recording = wasRecording;
 				Melder_flushError ("Graphics_play: unknown opcode (%d).\n%f %f", opcode, p [-1], p [1]);
diff --git a/sys/GuiThing.cpp b/sys/GuiThing.cpp
index 39b413c..c9acd46 100644
--- a/sys/GuiThing.cpp
+++ b/sys/GuiThing.cpp
@@ -70,6 +70,11 @@ void structGuiThing :: v_setSensitive (bool sensitive) {
 	#if gtk
 		gtk_widget_set_sensitive (GTK_WIDGET (d_widget), sensitive);
 	#elif cocoa
+		if ([(NSObject *) d_widget isKindOfClass: [NSControl class]]) {
+			[(NSControl *) d_widget setEnabled: sensitive];
+		} else if ([(NSObject *) d_widget isKindOfClass: [NSMenuItem class]]) {
+			[(NSMenuItem *) d_widget setEnabled: sensitive];
+		}
 	#elif motif
 		XtSetSensitive (d_widget, sensitive);
 	#endif
diff --git a/sys/melder.h b/sys/melder.h
index 9b93b61..16f204d 100644
--- a/sys/melder.h
+++ b/sys/melder.h
@@ -65,6 +65,8 @@ bool Melder_wcsequ_firstCharacterCaseInsensitive (const wchar_t *string1, const
 	#define NULL  ((void *) 0)
 #endif
 
+typedef struct { double red, green, blue, transparency; } double_rgbt;
+
 /********** NUMBER TO STRING CONVERSION **********/
 
 /*
diff --git a/sys/melder_audio.cpp b/sys/melder_audio.cpp
index 7c3ef9e..0affecc 100644
--- a/sys/melder_audio.cpp
+++ b/sys/melder_audio.cpp
@@ -508,27 +508,26 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 					if (my callback && ! my callback (my closure, my samplesPlayed))
 						interrupted = true;
 					if (my asynchronicity == kMelder_asynchronicityLevel_INTERRUPTABLE && ! interrupted) {
-						#if defined (macintosh)
-							#if useCarbon
-								EventRecord event;
-								if (EventAvail (keyDownMask, & event)) {
-									/*
-									* Remove the event, even if it was a different key.
-									* Otherwise, the key will block the future availability of the Escape key.
-									*/
-									FlushEvents (keyDownMask, 0);
-									/*
-									* Catch Escape and Command-period.
-									*/
-									if ((event. message & charCodeMask) == 27 ||
-										((event. modifiers & cmdKey) && (event. message & charCodeMask) == '.'))
-									{
-										my explicitStop = MelderAudio_EXPLICIT;
-										interrupted = true;
-									}
+						#if cocoa
+							
+						#elif defined (macintosh)
+							EventRecord event;
+							if (EventAvail (keyDownMask, & event)) {
+								/*
+								* Remove the event, even if it was a different key.
+								* Otherwise, the key will block the future availability of the Escape key.
+								*/
+								FlushEvents (keyDownMask, 0);
+								/*
+								* Catch Escape and Command-period.
+								*/
+								if ((event. message & charCodeMask) == 27 ||
+									((event. modifiers & cmdKey) && (event. message & charCodeMask) == '.'))
+								{
+									my explicitStop = MelderAudio_EXPLICIT;
+									interrupted = true;
 								}
-							#else
-							#endif
+							}
 						#elif defined (_WIN32)
 							MSG event;
 							if (PeekMessage (& event, 0, 0, 0, PM_REMOVE) && event. message == WM_KEYDOWN) {
@@ -574,27 +573,25 @@ void MelderAudio_play16 (const int16_t *buffer, long sampleRate, long numberOfSa
 					 * Do this on the lowest level that will work.
 					 */
 					if (my asynchronicity == kMelder_asynchronicityLevel_INTERRUPTABLE && ! interrupted) {
-						#if defined (macintosh)
-							#if useCarbon
-								EventRecord event;
-								if (EventAvail (keyDownMask, & event)) {
-									/*
-									* Remove the event, even if it was a different key.
-									* Otherwise, the key will block the future availability of the Escape key.
-									*/
-									FlushEvents (keyDownMask, 0);
-									/*
-									* Catch Escape and Command-period.
-									*/
-									if ((event. message & charCodeMask) == 27 ||
-										((event. modifiers & cmdKey) && (event. message & charCodeMask) == '.'))
-									{
-										my explicitStop = MelderAudio_EXPLICIT;
-										interrupted = true;
-									}
+						#if cocoa
+						#elif defined (macintosh)
+							EventRecord event;
+							if (EventAvail (keyDownMask, & event)) {
+								/*
+								* Remove the event, even if it was a different key.
+								* Otherwise, the key will block the future availability of the Escape key.
+								*/
+								FlushEvents (keyDownMask, 0);
+								/*
+								* Catch Escape and Command-period.
+								*/
+								if ((event. message & charCodeMask) == 27 ||
+									((event. modifiers & cmdKey) && (event. message & charCodeMask) == '.'))
+								{
+									my explicitStop = MelderAudio_EXPLICIT;
+									interrupted = true;
 								}
-							#else
-							#endif
+							}
 						#elif defined (_WIN32)
 							MSG event;
 							if (PeekMessage (& event, 0, 0, 0, PM_REMOVE) && event. message == WM_KEYDOWN) {
diff --git a/sys/oo_DESCRIPTION.h b/sys/oo_DESCRIPTION.h
index b28a095..0485756 100644
--- a/sys/oo_DESCRIPTION.h
+++ b/sys/oo_DESCRIPTION.h
@@ -252,6 +252,9 @@
 #define oo_STRUCT_VECTOR_FROM(Type,x,min,max)  { L"" #x, structwa, Melder_offsetof (ooSTRUCT, x), sizeof (struct struct##Type), L"" #Type, & struct##Type :: s_description, 1, L"" #min, L"" #max },
 #undef oo_STRUCT_VECTOR
 #define oo_STRUCT_VECTOR(Type,x,n)  { L"" #x, structwa, Melder_offsetof (ooSTRUCT, x), sizeof (struct struct##Type), L"" #Type, & struct##Type :: s_description, 1, (const wchar_t *) 0, L"" #n },
+#define oo_STRUCT_MATRIX_FROM(Type,x,r1,r2,c1,c2)  { L"" #x, structwa, Melder_offsetof (ooSTRUCT, x), sizeof (struct struct##Type), L"" #Type, & struct##Type :: s_description, 2, L"" #r1, L"" #r2, L"" #c1, L"" #c2 },
+#undef oo_STRUCT_MATRIX
+#define oo_STRUCT_MATRIX(Type,x,nrow,ncol)  { L"" #x, structwa, Melder_offsetof (ooSTRUCT, x), sizeof (struct struct##Type), L"" #Type, & struct##Type :: s_description, 2, (const wchar_t *) 0, L"" #nrow, (const wchar_t *) 0, L"" #ncol },
 
 #define oo_OBJECT(Type,version,x)  { L"" #x, objectwa, Melder_offsetof (ooSTRUCT, x), sizeof (Type), L"" #Type, & theClassInfo_##Type },
 #define oo_COLLECTION(Type,x,ItemType,version)  { L"" #x, collectionwa, Melder_offsetof (ooSTRUCT, x), sizeof (class struct##ItemType), L"" #Type, & theClassInfo_##Type, 0, (const wchar_t *) & theClassInfo_##ItemType },
diff --git a/sys/oo_DESTROY.h b/sys/oo_DESTROY.h
index af82455..b2959f4 100644
--- a/sys/oo_DESTROY.h
+++ b/sys/oo_DESTROY.h
@@ -81,7 +81,7 @@
 	if (x) { \
 		for (long i = row1; i <= row2; i ++) \
 			for (long j = col1; j <= col2; j ++) \
-				x [i] [j] -> destroy (); \
+				x [i] [j]. destroy (); \
 		NUMmatrix_free <struct##Type> (x, row1, col1); \
 	}
 
diff --git a/sys/oo_READ_BINARY.h b/sys/oo_READ_BINARY.h
index e3910c1..09fff68 100644
--- a/sys/oo_READ_BINARY.h
+++ b/sys/oo_READ_BINARY.h
@@ -1,6 +1,6 @@
 /* oo_READ_BINARY.h
  *
- * Copyright (C) 1994-2012 Paul Boersma
+ * Copyright (C) 1994-2012,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -108,6 +108,16 @@
 		} \
 	}
 
+#define oo_STRUCT_MATRIX_FROM(Type,x,row1,row2,col1,col2)  \
+	if (row2 >= row1 && col2 >= col1) { \
+		x = NUMmatrix <struct##Type> (row1, row2, col1, col2); \
+		for (long i = row1; i <= row2; i ++) { \
+			for (long j = col1; j <= col2; j ++) { \
+				x [i] [j]. readBinary (f); \
+			} \
+		} \
+	}
+
 #define oo_OBJECT(Class,version,x)  \
 	if (bingetex (f)) { \
 		long saveVersion = Thing_version; \
diff --git a/sys/oo_READ_TEXT.h b/sys/oo_READ_TEXT.h
index 2718669..c86c047 100644
--- a/sys/oo_READ_TEXT.h
+++ b/sys/oo_READ_TEXT.h
@@ -1,6 +1,6 @@
 /* oo_READ_TEXT.h
  *
- * Copyright (C) 1994-2012 Paul Boersma
+ * Copyright (C) 1994-2012,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -129,6 +129,16 @@
 		} \
 	}
 
+#define oo_STRUCT_MATRIX_FROM(Type,x,row1,row2,col1,col2)  \
+	if (row2 >= row1 && col2 >= col1) { \
+		x = NUMmatrix <struct##Type> (row1, row2, col1, col2); \
+		for (long i = row1; i <= row2; i ++) { \
+			for (long j = col1; j <= col2; j ++) { \
+				x [i] [j]. readText (a_text); \
+			} \
+		} \
+	}
+
 #define oo_OBJECT(Class,version,x)  \
 	if (texgetex (a_text) == 1) { \
 		long saveVersion = Thing_version; \
diff --git a/sys/oo_WRITE_BINARY.h b/sys/oo_WRITE_BINARY.h
index 39defa2..47f40c6 100644
--- a/sys/oo_WRITE_BINARY.h
+++ b/sys/oo_WRITE_BINARY.h
@@ -1,6 +1,6 @@
 /* oo_WRITE_BINARY.h
  *
- * Copyright (C) 1994-2012 Paul Boersma
+ * Copyright (C) 1994-2012,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -65,9 +65,8 @@
 		binput##storage (x [i], f);
 
 #define oo_STRINGx_VECTOR(storage,x,min,max)  \
-	if (max >= min) \
-		for (long i = min; i <= max; i ++) \
-			binput##storage (x [i], f);
+	for (long i = min; i <= max; i ++) \
+		binput##storage (x [i], f);
 
 #define oo_STRUCT(Type,x)  \
 	x. writeBinary (f);
@@ -81,10 +80,13 @@
 		x [i]. writeBinary (f);
 
 #define oo_STRUCT_VECTOR_FROM(Type,x,min,max)  \
-	if (max >= min) \
-		for (long i = min; i <= max; i ++) \
-			x [i]. writeBinary (f);
+	for (long i = min; i <= max; i ++) \
+		x [i]. writeBinary (f);
 
+#define oo_STRUCT_MATRIX_FROM(Type,x,row1,row2,col1,col2)  \
+	for (long i = row1; i <= row2; i ++) \
+		for (long j = col1; j <= col2; j ++) \
+			x [i] [j]. writeBinary (f);
 
 #define oo_OBJECT(Class,version,x)  \
 	binputex (x != NULL, f); \
diff --git a/sys/oo_WRITE_TEXT.h b/sys/oo_WRITE_TEXT.h
index 082be21..ebbc7a8 100644
--- a/sys/oo_WRITE_TEXT.h
+++ b/sys/oo_WRITE_TEXT.h
@@ -1,6 +1,6 @@
 /* oo_WRITE_TEXT.h
  *
- * Copyright (C) 1994-2012 Paul Boersma
+ * Copyright (C) 1994-2012,2013 Paul Boersma
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -116,6 +116,19 @@
 	} \
 	texexdent (file);
 
+#define oo_STRUCT_MATRIX_FROM(Type,x,row1,row2,col1,col2)  \
+	texputintro (file, L"" #x " [] []: ", row2 >= row1 ? NULL : L"(empty)", 0,0,0,0); \
+	for (long i = row1; i <= row2; i ++) { \
+		texputintro (file, L"" #x " [", Melder_integer (i), L"]:", 0,0,0); \
+		for (long j = col1; j <= col2; j ++) { \
+			texputintro (file, L"" #x " [", Melder_integer (i), L"] [", Melder_integer (j), L"]:", 0); \
+			x [i] [j]. writeText (file); \
+			texexdent (file); \
+		} \
+		texexdent (file); \
+	} \
+	texexdent (file);
+
 #define oo_OBJECT(Class,version,x)  \
 	texputex (file, x != NULL, L"" #x, 0,0,0,0,0); \
 	if (x) \
diff --git a/sys/praat_version.h b/sys/praat_version.h
index 8bb1b38..ca91d7d 100644
--- a/sys/praat_version.h
+++ b/sys/praat_version.h
@@ -1,5 +1,5 @@
-#define PRAAT_VERSION_STR 5.3.56
-#define PRAAT_VERSION_NUM 5356
+#define PRAAT_VERSION_STR 5.3.57
+#define PRAAT_VERSION_NUM 5357
 #define PRAAT_YEAR 2013
-#define PRAAT_MONTH September
-#define PRAAT_DAY 15
+#define PRAAT_MONTH October
+#define PRAAT_DAY 27

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



More information about the debian-med-commit mailing list